Hold to Confirm
A safety net for destructive actions — press and hold for 800ms while the ring fills clockwise, release early to cancel. Releases on full ring.
Hold to Confirm the 14th of 18 designs in the 18 CSS Close Buttons collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<button class="ccb-hold" aria-label="Hold to close"> <svg viewBox="0 0 40 40" class="ccb-hold-ring"><circle cx="20" cy="20" r="18" /></svg><span></span ><span></span> </button>
.ccb-hold {
width: 40px; height: 40px;
border: none; border-radius: 50%;
background: rgba(255,108,138,0.12);
position: relative; cursor: pointer;
transition: background 0.2s;
}
.ccb-hold:hover { background: rgba(255,108,138,0.2); }
.ccb-hold-ring {
position: absolute; inset: 0; width: 100%; height: 100%;
transform: rotate(-90deg); pointer-events: none;
}
.ccb-hold-ring circle {
fill: none; stroke: #ff6c8a; stroke-width: 2;
stroke-dasharray: 113; stroke-dashoffset: 113;
transition: stroke-dashoffset 0.05s linear;
}
.ccb-hold.is-holding .ccb-hold-ring circle {
transition: stroke-dashoffset 0.8s linear;
stroke-dashoffset: 0;
}
.ccb-hold span {
position: absolute; top: 50%; left: 50%;
width: 14px; height: 2px;
background: #ff6c8a; border-radius: 2px;
}
.ccb-hold span:nth-child(2) { transform: translate(-50%,-50%) rotate(45deg); }
.ccb-hold span:nth-child(3) { transform: translate(-50%,-50%) rotate(-45deg); }
.ccb-hold.is-confirmed { background: rgba(46,204,138,0.25); transform: scale(1.1); }
.ccb-hold.is-confirmed span { background: #2ecc8a; }
.ccb-hold.is-confirmed .ccb-hold-ring circle { stroke: #2ecc8a; } document.querySelectorAll('.ccb-hold').forEach(function(btn) {
var timer;
function start() {
btn.classList.add('is-holding');
timer = setTimeout(function() {
btn.classList.remove('is-holding');
btn.classList.add('is-confirmed');
setTimeout(function() { btn.classList.remove('is-confirmed'); }, 600);
}, 800);
}
function cancel() {
clearTimeout(timer);
btn.classList.remove('is-holding');
}
btn.addEventListener('mousedown', start);
btn.addEventListener('touchstart', start, { passive: true });
btn.addEventListener('mouseup', cancel);
btn.addEventListener('mouseleave', cancel);
btn.addEventListener('touchend', cancel);
});