6 CSS Countdown Timers 05 / 06

Circular Progress & Activity Timers (SaaS/Fitness/EdTech)

A radial SVG ring using stroke-dasharray/stroke-dashoffset that smoothly drains as time runs out, color-shifting green → amber → red.

Best forSaaS onboarding step timers, fitness app interval rounds, EdTech quiz countdowns, and webinar pre-roll holds.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<section class="cdt-cir" aria-label="Circular progress countdown timer demo">
  <div class="cdt-cir__panel">
    <div class="cdt-cir__quiz-label">Section 2 of 4</div>
    <div class="cdt-cir__quiz-title">Algebra Aptitude Quiz</div>

    <div class="cdt-cir__ring-wrap">
      <svg viewBox="0 0 240 240" aria-hidden="true">
        <circle class="cdt-cir__ring-bg" cx="120" cy="120" r="106"/>
        <circle class="cdt-cir__ring-fg" data-cdt-cir="ring" cx="120" cy="120" r="106"/>
      </svg>
      <div class="cdt-cir__ring-center" role="timer" aria-live="polite" aria-atomic="true">
        <div class="cdt-cir__time" data-cdt-cir="time">02:00</div>
        <div class="cdt-cir__cap">Remaining</div>
      </div>
    </div>

    <div class="cdt-cir__status" data-cdt-cir="status">
      <span class="cdt-cir__pip" aria-hidden="true"></span>
      <span data-cdt-cir="statusTxt">Time is running</span>
    </div>

    <div class="cdt-cir__controls">
      <button data-cdt-cir="reset" type="button">Reset</button>
      <button data-cdt-cir="toggle" type="button" class="cdt-cir__primary">Pause</button>
    </div>
  </div>
</section>
/* ─── 05 Circular Progress Timer — SVG ring drain ──────────── */
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;600;700;800&family=JetBrains+Mono:wght@600;700&display=swap');

.cdt-cir {
  --cdt-cir-bg: #0f1419;
  --cdt-cir-panel: #161d26;
  --cdt-cir-panel-line: #232c38;
  --cdt-cir-text: #e8eef4;
  --cdt-cir-muted: #7c8a9a;
  --cdt-cir-ring-bg: #232c38;
  --cdt-cir-green: #34d399;
  --cdt-cir-amber: #fbbf24;
  --cdt-cir-red: #f87171;

  position: relative;
  width: 100%;
  min-height: 560px;
  background: var(--cdt-cir-bg);
  background-image: radial-gradient(circle at 50% 0%, rgba(52,211,153,0.06), transparent 50%);
  font-family: 'Sora', sans-serif;
  color: var(--cdt-cir-text);
  display: flex; align-items: center; justify-content: center;
  padding: 36px 20px;
  box-sizing: border-box;
  overflow: hidden;
}
.cdt-cir *, .cdt-cir *::before, .cdt-cir *::after { box-sizing: border-box; margin: 0; padding: 0; }

.cdt-cir__panel {
  background: var(--cdt-cir-panel);
  border: 1px solid var(--cdt-cir-panel-line);
  border-radius: 28px;
  padding: 36px 36px 32px;
  width: 100%; max-width: 380px;
  text-align: center;
  box-shadow: 0 40px 80px -40px rgba(0,0,0,0.8);
}
.cdt-cir__quiz-label {
  font-size: 12px; letter-spacing: 2.5px; text-transform: uppercase;
  color: var(--cdt-cir-muted); margin-bottom: 6px; font-weight: 600;
}
.cdt-cir__quiz-title { font-size: 18px; font-weight: 700; margin-bottom: 26px; }

.cdt-cir__ring-wrap { position: relative; width: 220px; height: 220px; margin: 0 auto 26px; }
.cdt-cir__ring-wrap svg { transform: rotate(-90deg); width: 100%; height: 100%; }
.cdt-cir__ring-bg { fill: none; stroke: var(--cdt-cir-ring-bg); stroke-width: 14; }
.cdt-cir__ring-fg {
  fill: none; stroke: var(--cdt-cir-green); stroke-width: 14; stroke-linecap: round;
  transition: stroke-dashoffset 1s linear, stroke 0.4s ease;
  filter: drop-shadow(0 0 8px rgba(52,211,153,0.5));
}

.cdt-cir__ring-center {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
}
.cdt-cir__time {
  font-family: 'JetBrains Mono', monospace;
  font-size: 52px; font-weight: 700; line-height: 1; letter-spacing: -1px;
}
.cdt-cir__cap {
  font-size: 11px; letter-spacing: 2px; text-transform: uppercase;
  color: var(--cdt-cir-muted); margin-top: 8px;
}

.cdt-cir__status {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 600;
  padding: 7px 16px; border-radius: 20px;
  background: rgba(52,211,153,0.12); color: var(--cdt-cir-green);
  margin-bottom: 24px; transition: all 0.4s;
}
.cdt-cir__pip {
  width: 8px; height: 8px; border-radius: 50%; background: currentColor;
  animation: cdt-cir-blip 1.2s infinite;
}
@keyframes cdt-cir-blip { 50% { opacity: 0.3; } }

.cdt-cir__controls { display: flex; gap: 10px; }
.cdt-cir__controls button {
  flex: 1;
  font-family: 'Sora', sans-serif; font-weight: 600; font-size: 14px;
  padding: 12px; border-radius: 14px; cursor: pointer;
  border: 1px solid var(--cdt-cir-panel-line);
  background: transparent; color: var(--cdt-cir-text);
  transition: background 0.2s, transform 0.15s, border-color 0.2s;
}
.cdt-cir__controls button:hover         { background: var(--cdt-cir-panel-line); transform: translateY(-1px); }
.cdt-cir__controls button:focus-visible { outline: 2px solid var(--cdt-cir-green); outline-offset: 2px; }
.cdt-cir__primary {
  background: var(--cdt-cir-green) !important;
  color: #07120d !important;
  border-color: var(--cdt-cir-green) !important;
}
.cdt-cir__primary:hover { background: #2bbd86 !important; }

@media (prefers-reduced-motion: reduce) {
  .cdt-cir__ring-fg, .cdt-cir__pip, .cdt-cir__controls button { transition: none; animation: none; }
}
(() => {
  const root = document.querySelector('.cdt-cir');
  if (!root) return;
  const R = 106;
  const CIRC = 2 * Math.PI * R;
  const ring      = root.querySelector('[data-cdt-cir="ring"]');
  const timeEl    = root.querySelector('[data-cdt-cir="time"]');
  const statusEl  = root.querySelector('[data-cdt-cir="status"]');
  const statusTxt = root.querySelector('[data-cdt-cir="statusTxt"]');
  const toggleBtn = root.querySelector('[data-cdt-cir="toggle"]');
  const resetBtn  = root.querySelector('[data-cdt-cir="reset"]');
  ring.style.strokeDasharray = CIRC;

  const TOTAL = 120;
  let remaining = TOTAL;
  let running   = true;
  const pad = (n) => String(n).padStart(2, '0');

  function color() {
    const frac = remaining / TOTAL;
    if (frac > 0.5) return '#34d399';
    if (frac > 0.2) return '#fbbf24';
    return '#f87171';
  }

  function render() {
    timeEl.textContent = pad(Math.floor(remaining / 60)) + ':' + pad(remaining % 60);
    ring.style.strokeDashoffset = CIRC * (1 - remaining / TOTAL);
    const c = color();
    ring.style.stroke = c;
    ring.style.filter = 'drop-shadow(0 0 8px ' + c + ')';
    statusEl.style.background = c + '20';
    statusEl.style.color = c;
    if (remaining <= 0) {
      statusTxt.textContent = "Time's up!";
      running = false;
      toggleBtn.textContent = 'Start';
    }
  }

  function loop() {
    if (running && remaining > 0) {
      remaining--;
      render();
    }
  }

  toggleBtn.onclick = () => {
    if (remaining <= 0) remaining = TOTAL;
    running = !running;
    toggleBtn.textContent = running ? 'Pause' : 'Resume';
    statusTxt.textContent = running ? 'Time is running' : 'Paused';
  };
  resetBtn.onclick = () => {
    remaining = TOTAL;
    running = true;
    toggleBtn.textContent = 'Pause';
    statusTxt.textContent = 'Time is running';
    render();
  };

  render();
  setInterval(loop, 1000);
})();

Search CodeFronts

Loading…