Countdown Ring
Ring depletes continuously in real time. Color shifts green → amber → red as the deadline approaches. The depletion rate is the data. A static badge cannot know what time it is.
Countdown Ring the 15th of 30 designs in the 30 CSS Badges 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
<div class="cd-stage">
<div class="cd-wrap">
<div class="cd-event">Sprint Review</div>
<div class="cd-ring-wrap">
<svg viewBox="0 0 180 180" aria-hidden="true">
<circle class="cd-ring-track" cx="90" cy="90" r="81"/>
<circle class="cd-ring-fill" id="cd-ring" cx="90" cy="90" r="81" stroke="#0cce6b"/>
</svg>
<div class="cd-inner">
<div class="cd-time" id="cd-time">02:34:00</div>
<div class="cd-label">remaining</div>
</div>
</div>
<div class="cd-deadline" id="cd-deadline">Deadline · Today, 17:00</div>
</div>
</div> .cd-stage {
background: #fafaf7;
padding: 60px 48px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
justify-content: center;
min-height: 400px;
}
.cd-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
}
.cd-ring-wrap {
position: relative;
width: 180px;
height: 180px;
}
.cd-ring-wrap svg {
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.cd-ring-track {
fill: none;
stroke: #e8e4da;
stroke-width: 9;
}
.cd-ring-fill {
fill: none;
stroke-width: 9;
stroke-linecap: round;
stroke-dasharray: 508;
stroke-dashoffset: 0;
transition: stroke-dashoffset 1s linear, stroke 0.8s ease;
}
.cd-inner {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
}
.cd-time {
font-family: ui-monospace, "JetBrains Mono", monospace;
font-size: 26px;
font-weight: 700;
color: #1a1612;
letter-spacing: -0.02em;
line-height: 1;
}
.cd-label {
font-family: system-ui, "Bricolage Grotesque", sans-serif;
font-size: 9px;
letter-spacing: 0.22em;
text-transform: uppercase;
color: #4a4239;
}
.cd-deadline {
font-family: system-ui, "Bricolage Grotesque", sans-serif;
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #aaa;
text-align: center;
}
.cd-event {
font-family: Georgia, "Fraunces", serif;
font-size: 20px;
font-style: italic;
color: #1a1612;
text-align: center;
} // Countdown ring — 4h total sprint, ~2h34m initially remaining.
// Ring depletes continuously, color shifts at 50% and 20% thresholds.
(function () {
var TOTAL_MS = 4 * 3600 * 1000;
var deadline = new Date(Date.now() + 2 * 3600 * 1000 + 34 * 60 * 1000);
var ring = document.getElementById('cd-ring');
var timeEl = document.getElementById('cd-time');
var deadEl = document.getElementById('cd-deadline');
var CIRCUM = 2 * Math.PI * 81;
function pad2(n) { return String(n).padStart(2, '0'); }
function tick() {
var remaining = Math.max(0, deadline - Date.now());
var frac = remaining / TOTAL_MS;
ring.style.strokeDashoffset = CIRCUM * (1 - frac);
ring.style.stroke = frac > 0.5 ? '#0cce6b' : frac > 0.2 ? '#ffa400' : '#ff4e42';
var h = Math.floor(remaining / 3600000);
var m = Math.floor((remaining % 3600000) / 60000);
var s = Math.floor((remaining % 60000) / 1000);
timeEl.textContent = pad2(h) + ':' + pad2(m) + ':' + pad2(s);
deadEl.textContent = 'Deadline · Today, ' + deadline.getHours() + ':' + pad2(deadline.getMinutes());
}
tick();
setInterval(tick, 1000);
})();