04 / 22
All 22 in 22 Tailwind CSS Countdown Timers
- 01Accessible Tailwind CSS Countdown Timer with ARIA Live Region
- 02Pure Tailwind CSS Countdown Timer Without JavaScript
- 03Tailwind Session Timeout Warning Modal with Countdown
- 04Tailwind Circular Countdown Timer with SVG Progress Ring
- 05Tailwind OTP Input with Resend Code Countdown Timer
- 06Tailwind Rate Limit Button with Retry-After Countdown
- 07Tailwind Auction Countdown Timer with Anti-Snipe Extension
- 08Tailwind Delivery ETA Countdown with Order Tracking Steps
- 09Tailwind Waitlist Landing Page with Launch Countdown
- 10Tailwind Webinar Countdown Timer with Time Zone Display
- 11Tailwind Appointment Reminder Card with Countdown
- 12Tailwind Exam Timer with Time Remaining Warnings
- 13Tailwind Free Trial Countdown Banner for SaaS Apps
- 14Tailwind Scheduled Maintenance Countdown Status Page
- 15Tailwind Virtual Waiting Room Queue Countdown
- 16Tailwind Daily Streak Countdown Widget for Habit Apps
- 17Tailwind Flash Sale Countdown Timer for Product Pages
- 18Tailwind Sticky Announcement Bar with Countdown Timer
- 19Tailwind Shopping Cart Reservation Countdown Timer
- 20Tailwind Coming Soon Page with Launch Countdown Timer
- 21Tailwind Interval Workout Timer with Circular Progress
- 22Tailwind Retro Flip Clock Countdown Timer
Tailwind Circular Countdown Timer with SVG Progress Ring
A five-minute hold timer whose SVG ring empties as the window closes — time depleting, not task progress. pathLength="100" makes the dash maths a percentage, the stroke steps from teal to amber to red at 50% and 20%, and the legend chips now light up in sync, all with stroke-*, fill-none and @theme animate tokens. Zero JavaScript.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcd-04 w-full min-h-svh grid place-items-center bg-tcd04-page bg-[radial-gradient(60%_50%_at_50%_45%,var(--color-tcd04-glow),transparent_70%)] px-5 py-14 font-tcd04-sans text-tcd04-ink" aria-labelledby="tcd-04-title">
<div class="grid w-full max-w-[640px] justify-items-center gap-7 text-center">
<header class="min-w-0">
<h2 id="tcd-04-title" class="mb-1.5 font-tcd04-display text-[clamp(20px,4vw,30px)] font-medium uppercase leading-tight tracking-[0.06em]">Hold time remaining</h2>
<p class="text-[13px] text-tcd04-mut">Orbit support · Priya Sharma is next · window <time datetime="PT5M">5 minutes</time></p>
</header>
<div role="timer" aria-label="Five minute hold timer, time remaining" class="relative aspect-square w-[clamp(216px,58vw,320px)] rounded-full bg-tcd04-panel/60 shadow-[inset_0_0_0_1px_var(--color-tcd04-rule),0_40px_80px_-40px_rgb(0_0_0/0.9)]">
<svg viewBox="0 0 120 120" aria-hidden="true" class="size-full -rotate-90 drop-shadow-[0_0_16px_rgb(56_217_200/0.3)] motion-reduce:drop-shadow-none">
<circle cx="60" cy="60" r="52" pathLength="100" class="fill-none stroke-tcd04-rule [stroke-width:9]"/>
<circle cx="60" cy="60" r="52" pathLength="100" class="fill-none stroke-tcd04-calm [stroke-dasharray:100] [stroke-linecap:round] [stroke-width:9] animate-tcd04-ring"/>
</svg>
<div class="absolute inset-0 grid place-content-center gap-1">
<p aria-hidden="true" class="flex items-baseline justify-center gap-px font-tcd04-display text-[clamp(44px,13vw,68px)] font-medium leading-none tabular-nums">
<em class="not-italic animate-tcd04-m [counter-reset:tcd04m_var(--tcd04-m)] before:content-[counter(tcd04m)]"></em><span class="opacity-45">:</span><em class="not-italic animate-tcd04-st [counter-reset:tcd04st_var(--tcd04-st)] before:content-[counter(tcd04st)]"></em><em class="not-italic animate-tcd04-su [counter-reset:tcd04su_var(--tcd04-su)] before:content-[counter(tcd04su)]"></em>
</p>
<p class="text-[10.5px] uppercase tracking-[0.24em] text-tcd04-mut">remaining</p>
</div>
<div role="status" class="invisible absolute inset-0 grid place-content-center gap-1 rounded-full bg-tcd04-panel opacity-0 animate-tcd04-done">
<strong class="font-tcd04-display text-[clamp(22px,6vw,30px)] font-medium uppercase tracking-[0.05em] text-tcd04-calm">Connecting</strong>
<span class="text-xs text-tcd04-mut">Agent picking up</span>
</div>
</div>
<ul class="flex flex-wrap justify-center gap-2">
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-lc motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-calm"></span>Plenty of time · above 50%</li>
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-lw motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-warn"></span>Wrap up · under 50%</li>
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-ld motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-danger"></span>Final seconds · under 20%</li>
</ul>
<p class="max-w-[52ch] text-[12.5px] leading-relaxed text-tcd04-mut text-pretty">Time-depleting, not task-progress: the arc shrinks because the window is closing, and the legend lights the live band so the state never depends on colour alone. Pure CSS, so it measures five minutes from render.</p>
</div>
</section><section class="tcd-04 w-full min-h-svh grid place-items-center bg-tcd04-page bg-[radial-gradient(60%_50%_at_50%_45%,var(--color-tcd04-glow),transparent_70%)] px-5 py-14 font-tcd04-sans text-tcd04-ink" aria-labelledby="tcd-04-title">
<div class="grid w-full max-w-[640px] justify-items-center gap-7 text-center">
<header class="min-w-0">
<h2 id="tcd-04-title" class="mb-1.5 font-tcd04-display text-[clamp(20px,4vw,30px)] font-medium uppercase leading-tight tracking-[0.06em]">Hold time remaining</h2>
<p class="text-[13px] text-tcd04-mut">Orbit support · Priya Sharma is next · window <time datetime="PT5M">5 minutes</time></p>
</header>
<div role="timer" aria-label="Five minute hold timer, time remaining" class="relative aspect-square w-[clamp(216px,58vw,320px)] rounded-full bg-tcd04-panel/60 shadow-[inset_0_0_0_1px_var(--color-tcd04-rule),0_40px_80px_-40px_rgb(0_0_0/0.9)]">
<svg viewBox="0 0 120 120" aria-hidden="true" class="size-full -rotate-90 drop-shadow-[0_0_16px_rgb(56_217_200/0.3)] motion-reduce:drop-shadow-none">
<circle cx="60" cy="60" r="52" pathLength="100" class="fill-none stroke-tcd04-rule [stroke-width:9]"/>
<circle cx="60" cy="60" r="52" pathLength="100" class="fill-none stroke-tcd04-calm [stroke-dasharray:100] [stroke-linecap:round] [stroke-width:9] animate-tcd04-ring"/>
</svg>
<div class="absolute inset-0 grid place-content-center gap-1">
<p aria-hidden="true" class="flex items-baseline justify-center gap-px font-tcd04-display text-[clamp(44px,13vw,68px)] font-medium leading-none tabular-nums">
<em class="not-italic animate-tcd04-m [counter-reset:tcd04m_var(--tcd04-m)] before:content-[counter(tcd04m)]"></em><span class="opacity-45">:</span><em class="not-italic animate-tcd04-st [counter-reset:tcd04st_var(--tcd04-st)] before:content-[counter(tcd04st)]"></em><em class="not-italic animate-tcd04-su [counter-reset:tcd04su_var(--tcd04-su)] before:content-[counter(tcd04su)]"></em>
</p>
<p class="text-[10.5px] uppercase tracking-[0.24em] text-tcd04-mut">remaining</p>
</div>
<div role="status" class="invisible absolute inset-0 grid place-content-center gap-1 rounded-full bg-tcd04-panel opacity-0 animate-tcd04-done">
<strong class="font-tcd04-display text-[clamp(22px,6vw,30px)] font-medium uppercase tracking-[0.05em] text-tcd04-calm">Connecting</strong>
<span class="text-xs text-tcd04-mut">Agent picking up</span>
</div>
</div>
<ul class="flex flex-wrap justify-center gap-2">
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-lc motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-calm"></span>Plenty of time · above 50%</li>
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-lw motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-warn"></span>Wrap up · under 50%</li>
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-ld motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-danger"></span>Final seconds · under 20%</li>
</ul>
<p class="max-w-[52ch] text-[12.5px] leading-relaxed text-tcd04-mut text-pretty">Time-depleting, not task-progress: the arc shrinks because the window is closing, and the legend lights the live band so the state never depends on colour alone. Pure CSS, so it measures five minutes from render.</p>
</div>
</section>@import "tailwindcss";
@property --tcd04-m {
syntax: '<integer>';
inherits: false;
initial-value: 4;
}
@property --tcd04-st {
syntax: '<integer>';
inherits: false;
initial-value: 5;
}
@property --tcd04-su {
syntax: '<integer>';
inherits: false;
initial-value: 9;
}
@theme {
--color-tcd04-page: #070c1a;
--color-tcd04-page: oklch(0.16 0.04 265);
--color-tcd04-glow: #101a36;
--color-tcd04-glow: oklch(0.24 0.06 265);
--color-tcd04-panel: #0d1428;
--color-tcd04-panel: oklch(0.21 0.05 264);
--color-tcd04-ink: #eaf0ff;
--color-tcd04-ink: oklch(0.95 0.02 265);
--color-tcd04-mut: #95a3c7;
--color-tcd04-mut: oklch(0.73 0.05 268);
--color-tcd04-rule: #1c2745;
--color-tcd04-rule: oklch(0.3 0.05 265);
--color-tcd04-calm: #38d9c8;
--color-tcd04-calm: oklch(0.8 0.13 185);
--color-tcd04-warn: #f2b23c;
--color-tcd04-warn: oklch(0.8 0.15 78);
--color-tcd04-danger: #ff5a63;
--color-tcd04-danger: oklch(0.68 0.2 20);
--font-tcd04-display: 'Oswald', 'Archivo Narrow', sans-serif;
--font-tcd04-sans: 'DM Sans', system-ui, sans-serif;
--animate-tcd04-ring: tcd04-arc 300s linear forwards, tcd04-hue 300s steps(1, end) forwards;
--animate-tcd04-m: tcd04-m 300s steps(5, end) forwards;
--animate-tcd04-st: tcd04-st 60s steps(6, end) infinite;
--animate-tcd04-su: tcd04-su 10s steps(10, end) infinite;
--animate-tcd04-done: tcd04-done 0.01s linear 300s forwards;
--animate-tcd04-lc: tcd04-lc 300s steps(1, end) forwards;
--animate-tcd04-lw: tcd04-lw 300s steps(1, end) forwards;
--animate-tcd04-ld: tcd04-ld 300s steps(1, end) forwards;
@keyframes tcd04-arc {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 100;
}
}
@keyframes tcd04-hue {
0% {
stroke: var(--color-tcd04-calm);
stroke-width: 9;
}
50% {
stroke: var(--color-tcd04-warn);
stroke-width: 9;
}
80%,
100% {
stroke: var(--color-tcd04-danger);
stroke-width: 11;
}
}
@keyframes tcd04-m {
from {
--tcd04-m: 4;
}
to {
--tcd04-m: -1;
}
}
@keyframes tcd04-st {
from {
--tcd04-st: 5;
}
to {
--tcd04-st: -1;
}
}
@keyframes tcd04-su {
from {
--tcd04-su: 9;
}
to {
--tcd04-su: -1;
}
}
@keyframes tcd04-done {
to {
opacity: 1;
visibility: visible;
}
}
@keyframes tcd04-lc {
0% {
opacity: 1;
}
50%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-lw {
0% {
opacity: 0.4;
}
50% {
opacity: 1;
}
80%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-ld {
0% {
opacity: 0.4;
}
80%,
100% {
opacity: 1;
}
}
}
.tcd-04 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@property --tcd04-m {
syntax: '<integer>';
inherits: false;
initial-value: 4;
}
@property --tcd04-st {
syntax: '<integer>';
inherits: false;
initial-value: 5;
}
@property --tcd04-su {
syntax: '<integer>';
inherits: false;
initial-value: 9;
}
@theme {
--color-tcd04-page: #070c1a;
--color-tcd04-page: oklch(0.16 0.04 265);
--color-tcd04-glow: #101a36;
--color-tcd04-glow: oklch(0.24 0.06 265);
--color-tcd04-panel: #0d1428;
--color-tcd04-panel: oklch(0.21 0.05 264);
--color-tcd04-ink: #eaf0ff;
--color-tcd04-ink: oklch(0.95 0.02 265);
--color-tcd04-mut: #95a3c7;
--color-tcd04-mut: oklch(0.73 0.05 268);
--color-tcd04-rule: #1c2745;
--color-tcd04-rule: oklch(0.3 0.05 265);
--color-tcd04-calm: #38d9c8;
--color-tcd04-calm: oklch(0.8 0.13 185);
--color-tcd04-warn: #f2b23c;
--color-tcd04-warn: oklch(0.8 0.15 78);
--color-tcd04-danger: #ff5a63;
--color-tcd04-danger: oklch(0.68 0.2 20);
--font-tcd04-display: 'Oswald', 'Archivo Narrow', sans-serif;
--font-tcd04-sans: 'DM Sans', system-ui, sans-serif;
--animate-tcd04-ring: tcd04-arc 300s linear forwards, tcd04-hue 300s steps(1, end) forwards;
--animate-tcd04-m: tcd04-m 300s steps(5, end) forwards;
--animate-tcd04-st: tcd04-st 60s steps(6, end) infinite;
--animate-tcd04-su: tcd04-su 10s steps(10, end) infinite;
--animate-tcd04-done: tcd04-done 0.01s linear 300s forwards;
--animate-tcd04-lc: tcd04-lc 300s steps(1, end) forwards;
--animate-tcd04-lw: tcd04-lw 300s steps(1, end) forwards;
--animate-tcd04-ld: tcd04-ld 300s steps(1, end) forwards;
@keyframes tcd04-arc {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 100;
}
}
@keyframes tcd04-hue {
0% {
stroke: var(--color-tcd04-calm);
stroke-width: 9;
}
50% {
stroke: var(--color-tcd04-warn);
stroke-width: 9;
}
80%,
100% {
stroke: var(--color-tcd04-danger);
stroke-width: 11;
}
}
@keyframes tcd04-m {
from {
--tcd04-m: 4;
}
to {
--tcd04-m: -1;
}
}
@keyframes tcd04-st {
from {
--tcd04-st: 5;
}
to {
--tcd04-st: -1;
}
}
@keyframes tcd04-su {
from {
--tcd04-su: 9;
}
to {
--tcd04-su: -1;
}
}
@keyframes tcd04-done {
to {
opacity: 1;
visibility: visible;
}
}
@keyframes tcd04-lc {
0% {
opacity: 1;
}
50%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-lw {
0% {
opacity: 0.4;
}
50% {
opacity: 1;
}
80%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-ld {
0% {
opacity: 0.4;
}
80%,
100% {
opacity: 1;
}
}
}
.tcd-04 {
width: 100%;
min-height: 100svh;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@property --tcd04-m {
syntax: '<integer>';
inherits: false;
initial-value: 4;
}
@property --tcd04-st {
syntax: '<integer>';
inherits: false;
initial-value: 5;
}
@property --tcd04-su {
syntax: '<integer>';
inherits: false;
initial-value: 9;
}
@theme {
--color-tcd04-page: #070c1a;
--color-tcd04-page: oklch(0.16 0.04 265);
--color-tcd04-glow: #101a36;
--color-tcd04-glow: oklch(0.24 0.06 265);
--color-tcd04-panel: #0d1428;
--color-tcd04-panel: oklch(0.21 0.05 264);
--color-tcd04-ink: #eaf0ff;
--color-tcd04-ink: oklch(0.95 0.02 265);
--color-tcd04-mut: #95a3c7;
--color-tcd04-mut: oklch(0.73 0.05 268);
--color-tcd04-rule: #1c2745;
--color-tcd04-rule: oklch(0.3 0.05 265);
--color-tcd04-calm: #38d9c8;
--color-tcd04-calm: oklch(0.8 0.13 185);
--color-tcd04-warn: #f2b23c;
--color-tcd04-warn: oklch(0.8 0.15 78);
--color-tcd04-danger: #ff5a63;
--color-tcd04-danger: oklch(0.68 0.2 20);
--font-tcd04-display: 'Oswald', 'Archivo Narrow', sans-serif;
--font-tcd04-sans: 'DM Sans', system-ui, sans-serif;
--animate-tcd04-ring: tcd04-arc 300s linear forwards, tcd04-hue 300s steps(1, end) forwards;
--animate-tcd04-m: tcd04-m 300s steps(5, end) forwards;
--animate-tcd04-st: tcd04-st 60s steps(6, end) infinite;
--animate-tcd04-su: tcd04-su 10s steps(10, end) infinite;
--animate-tcd04-done: tcd04-done 0.01s linear 300s forwards;
--animate-tcd04-lc: tcd04-lc 300s steps(1, end) forwards;
--animate-tcd04-lw: tcd04-lw 300s steps(1, end) forwards;
--animate-tcd04-ld: tcd04-ld 300s steps(1, end) forwards;
@keyframes tcd04-arc {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 100;
}
}
@keyframes tcd04-hue {
0% {
stroke: var(--color-tcd04-calm);
stroke-width: 9;
}
50% {
stroke: var(--color-tcd04-warn);
stroke-width: 9;
}
80%,
100% {
stroke: var(--color-tcd04-danger);
stroke-width: 11;
}
}
@keyframes tcd04-m {
from {
--tcd04-m: 4;
}
to {
--tcd04-m: -1;
}
}
@keyframes tcd04-st {
from {
--tcd04-st: 5;
}
to {
--tcd04-st: -1;
}
}
@keyframes tcd04-su {
from {
--tcd04-su: 9;
}
to {
--tcd04-su: -1;
}
}
@keyframes tcd04-done {
to {
opacity: 1;
visibility: visible;
}
}
@keyframes tcd04-lc {
0% {
opacity: 1;
}
50%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-lw {
0% {
opacity: 0.4;
}
50% {
opacity: 1;
}
80%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-ld {
0% {
opacity: 0.4;
}
80%,
100% {
opacity: 1;
}
}
}
.tcd-04 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@property --tcd04-m {
syntax: '<integer>';
inherits: false;
initial-value: 4;
}
@property --tcd04-st {
syntax: '<integer>';
inherits: false;
initial-value: 5;
}
@property --tcd04-su {
syntax: '<integer>';
inherits: false;
initial-value: 9;
}
@theme {
--color-tcd04-page: #070c1a;
--color-tcd04-page: oklch(0.16 0.04 265);
--color-tcd04-glow: #101a36;
--color-tcd04-glow: oklch(0.24 0.06 265);
--color-tcd04-panel: #0d1428;
--color-tcd04-panel: oklch(0.21 0.05 264);
--color-tcd04-ink: #eaf0ff;
--color-tcd04-ink: oklch(0.95 0.02 265);
--color-tcd04-mut: #95a3c7;
--color-tcd04-mut: oklch(0.73 0.05 268);
--color-tcd04-rule: #1c2745;
--color-tcd04-rule: oklch(0.3 0.05 265);
--color-tcd04-calm: #38d9c8;
--color-tcd04-calm: oklch(0.8 0.13 185);
--color-tcd04-warn: #f2b23c;
--color-tcd04-warn: oklch(0.8 0.15 78);
--color-tcd04-danger: #ff5a63;
--color-tcd04-danger: oklch(0.68 0.2 20);
--font-tcd04-display: 'Oswald', 'Archivo Narrow', sans-serif;
--font-tcd04-sans: 'DM Sans', system-ui, sans-serif;
--animate-tcd04-ring: tcd04-arc 300s linear forwards, tcd04-hue 300s steps(1, end) forwards;
--animate-tcd04-m: tcd04-m 300s steps(5, end) forwards;
--animate-tcd04-st: tcd04-st 60s steps(6, end) infinite;
--animate-tcd04-su: tcd04-su 10s steps(10, end) infinite;
--animate-tcd04-done: tcd04-done 0.01s linear 300s forwards;
--animate-tcd04-lc: tcd04-lc 300s steps(1, end) forwards;
--animate-tcd04-lw: tcd04-lw 300s steps(1, end) forwards;
--animate-tcd04-ld: tcd04-ld 300s steps(1, end) forwards;
@keyframes tcd04-arc {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 100;
}
}
@keyframes tcd04-hue {
0% {
stroke: var(--color-tcd04-calm);
stroke-width: 9;
}
50% {
stroke: var(--color-tcd04-warn);
stroke-width: 9;
}
80%,
100% {
stroke: var(--color-tcd04-danger);
stroke-width: 11;
}
}
@keyframes tcd04-m {
from {
--tcd04-m: 4;
}
to {
--tcd04-m: -1;
}
}
@keyframes tcd04-st {
from {
--tcd04-st: 5;
}
to {
--tcd04-st: -1;
}
}
@keyframes tcd04-su {
from {
--tcd04-su: 9;
}
to {
--tcd04-su: -1;
}
}
@keyframes tcd04-done {
to {
opacity: 1;
visibility: visible;
}
}
@keyframes tcd04-lc {
0% {
opacity: 1;
}
50%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-lw {
0% {
opacity: 0.4;
}
50% {
opacity: 1;
}
80%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-ld {
0% {
opacity: 0.4;
}
80%,
100% {
opacity: 1;
}
}
}
.tcd-04 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;600&family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Oswald:wght@400;500;600&family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- SVG paint moved into utilities:
fill-none,stroke-tcd04-calmand arbitrary[stroke-dasharray:100]/[stroke-width:9]; the two arc animations share one--animate-tcd04-ringtoken. - Bug fix: as in demo 02, the counters reset on the element rather than inside
::before, where a non-inheriting@propertyis invisible. - UI upgrade: the legend became pill chips whose opacity animates on the same
steps(1,end)schedule as the stroke, so the live band is readable without colour perception. - UI upgrade: the dial sits on a recessed panel disc with an inset hairline, muted copy rose to
0.73lightness (6.8:1), and the sans moved to DM Sans to pair with Oswald digits.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Circular Countdown Timer with SVG Progress Ring
Source: https://codefronts.com/snippets/tailwind-css-countdown-timers/tailwind-circular-countdown-timer-svg-ring/
A five-minute hold timer whose SVG ring empties as the window closes — time depleting, not task progress. pathLength="100" makes the dash maths a percentage, the stroke steps from teal to amber to red at 50% and 20%, and the legend chips now light up in sync, all with stroke-*, fill-none and @theme animate tokens. Zero JavaScript.
## HTML
```html
<section class="tcd-04 w-full min-h-svh grid place-items-center bg-tcd04-page bg-[radial-gradient(60%_50%_at_50%_45%,var(--color-tcd04-glow),transparent_70%)] px-5 py-14 font-tcd04-sans text-tcd04-ink" aria-labelledby="tcd-04-title">
<div class="grid w-full max-w-[640px] justify-items-center gap-7 text-center">
<header class="min-w-0">
<h2 id="tcd-04-title" class="mb-1.5 font-tcd04-display text-[clamp(20px,4vw,30px)] font-medium uppercase leading-tight tracking-[0.06em]">Hold time remaining</h2>
<p class="text-[13px] text-tcd04-mut">Orbit support · Priya Sharma is next · window <time datetime="PT5M">5 minutes</time></p>
</header>
<div role="timer" aria-label="Five minute hold timer, time remaining" class="relative aspect-square w-[clamp(216px,58vw,320px)] rounded-full bg-tcd04-panel/60 shadow-[inset_0_0_0_1px_var(--color-tcd04-rule),0_40px_80px_-40px_rgb(0_0_0/0.9)]">
<svg viewBox="0 0 120 120" aria-hidden="true" class="size-full -rotate-90 drop-shadow-[0_0_16px_rgb(56_217_200/0.3)] motion-reduce:drop-shadow-none">
<circle cx="60" cy="60" r="52" pathLength="100" class="fill-none stroke-tcd04-rule [stroke-width:9]"/>
<circle cx="60" cy="60" r="52" pathLength="100" class="fill-none stroke-tcd04-calm [stroke-dasharray:100] [stroke-linecap:round] [stroke-width:9] animate-tcd04-ring"/>
</svg>
<div class="absolute inset-0 grid place-content-center gap-1">
<p aria-hidden="true" class="flex items-baseline justify-center gap-px font-tcd04-display text-[clamp(44px,13vw,68px)] font-medium leading-none tabular-nums">
<em class="not-italic animate-tcd04-m [counter-reset:tcd04m_var(--tcd04-m)] before:content-[counter(tcd04m)]"></em><span class="opacity-45">:</span><em class="not-italic animate-tcd04-st [counter-reset:tcd04st_var(--tcd04-st)] before:content-[counter(tcd04st)]"></em><em class="not-italic animate-tcd04-su [counter-reset:tcd04su_var(--tcd04-su)] before:content-[counter(tcd04su)]"></em>
</p>
<p class="text-[10.5px] uppercase tracking-[0.24em] text-tcd04-mut">remaining</p>
</div>
<div role="status" class="invisible absolute inset-0 grid place-content-center gap-1 rounded-full bg-tcd04-panel opacity-0 animate-tcd04-done">
<strong class="font-tcd04-display text-[clamp(22px,6vw,30px)] font-medium uppercase tracking-[0.05em] text-tcd04-calm">Connecting</strong>
<span class="text-xs text-tcd04-mut">Agent picking up</span>
</div>
</div>
<ul class="flex flex-wrap justify-center gap-2">
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-lc motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-calm"></span>Plenty of time · above 50%</li>
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-lw motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-warn"></span>Wrap up · under 50%</li>
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-ld motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-danger"></span>Final seconds · under 20%</li>
</ul>
<p class="max-w-[52ch] text-[12.5px] leading-relaxed text-tcd04-mut text-pretty">Time-depleting, not task-progress: the arc shrinks because the window is closing, and the legend lights the live band so the state never depends on colour alone. Pure CSS, so it measures five minutes from render.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@property --tcd04-m {
syntax: '<integer>';
inherits: false;
initial-value: 4;
}
@property --tcd04-st {
syntax: '<integer>';
inherits: false;
initial-value: 5;
}
@property --tcd04-su {
syntax: '<integer>';
inherits: false;
initial-value: 9;
}
@theme {
--color-tcd04-page: #070c1a;
--color-tcd04-page: oklch(0.16 0.04 265);
--color-tcd04-glow: #101a36;
--color-tcd04-glow: oklch(0.24 0.06 265);
--color-tcd04-panel: #0d1428;
--color-tcd04-panel: oklch(0.21 0.05 264);
--color-tcd04-ink: #eaf0ff;
--color-tcd04-ink: oklch(0.95 0.02 265);
--color-tcd04-mut: #95a3c7;
--color-tcd04-mut: oklch(0.73 0.05 268);
--color-tcd04-rule: #1c2745;
--color-tcd04-rule: oklch(0.3 0.05 265);
--color-tcd04-calm: #38d9c8;
--color-tcd04-calm: oklch(0.8 0.13 185);
--color-tcd04-warn: #f2b23c;
--color-tcd04-warn: oklch(0.8 0.15 78);
--color-tcd04-danger: #ff5a63;
--color-tcd04-danger: oklch(0.68 0.2 20);
--font-tcd04-display: 'Oswald', 'Archivo Narrow', sans-serif;
--font-tcd04-sans: 'DM Sans', system-ui, sans-serif;
--animate-tcd04-ring: tcd04-arc 300s linear forwards, tcd04-hue 300s steps(1, end) forwards;
--animate-tcd04-m: tcd04-m 300s steps(5, end) forwards;
--animate-tcd04-st: tcd04-st 60s steps(6, end) infinite;
--animate-tcd04-su: tcd04-su 10s steps(10, end) infinite;
--animate-tcd04-done: tcd04-done 0.01s linear 300s forwards;
--animate-tcd04-lc: tcd04-lc 300s steps(1, end) forwards;
--animate-tcd04-lw: tcd04-lw 300s steps(1, end) forwards;
--animate-tcd04-ld: tcd04-ld 300s steps(1, end) forwards;
@keyframes tcd04-arc {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 100;
}
}
@keyframes tcd04-hue {
0% {
stroke: var(--color-tcd04-calm);
stroke-width: 9;
}
50% {
stroke: var(--color-tcd04-warn);
stroke-width: 9;
}
80%,
100% {
stroke: var(--color-tcd04-danger);
stroke-width: 11;
}
}
@keyframes tcd04-m {
from {
--tcd04-m: 4;
}
to {
--tcd04-m: -1;
}
}
@keyframes tcd04-st {
from {
--tcd04-st: 5;
}
to {
--tcd04-st: -1;
}
}
@keyframes tcd04-su {
from {
--tcd04-su: 9;
}
to {
--tcd04-su: -1;
}
}
@keyframes tcd04-done {
to {
opacity: 1;
visibility: visible;
}
}
@keyframes tcd04-lc {
0% {
opacity: 1;
}
50%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-lw {
0% {
opacity: 0.4;
}
50% {
opacity: 1;
}
80%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-ld {
0% {
opacity: 0.4;
}
80%,
100% {
opacity: 1;
}
}
}
.tcd-04 {
width: 100%;
min-height: 100svh;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Circular Countdown Timer with SVG Progress Ring
Source: https://codefronts.com/snippets/tailwind-css-countdown-timers/tailwind-circular-countdown-timer-svg-ring/
A five-minute hold timer whose SVG ring empties as the window closes — time depleting, not task progress. pathLength="100" makes the dash maths a percentage, the stroke steps from teal to amber to red at 50% and 20%, and the legend chips now light up in sync, all with stroke-*, fill-none and @theme animate tokens. Zero JavaScript.
## HTML
```html
<section class="tcd-04 w-full min-h-svh grid place-items-center bg-tcd04-page bg-[radial-gradient(60%_50%_at_50%_45%,var(--color-tcd04-glow),transparent_70%)] px-5 py-14 font-tcd04-sans text-tcd04-ink" aria-labelledby="tcd-04-title">
<div class="grid w-full max-w-[640px] justify-items-center gap-7 text-center">
<header class="min-w-0">
<h2 id="tcd-04-title" class="mb-1.5 font-tcd04-display text-[clamp(20px,4vw,30px)] font-medium uppercase leading-tight tracking-[0.06em]">Hold time remaining</h2>
<p class="text-[13px] text-tcd04-mut">Orbit support · Priya Sharma is next · window <time datetime="PT5M">5 minutes</time></p>
</header>
<div role="timer" aria-label="Five minute hold timer, time remaining" class="relative aspect-square w-[clamp(216px,58vw,320px)] rounded-full bg-tcd04-panel/60 shadow-[inset_0_0_0_1px_var(--color-tcd04-rule),0_40px_80px_-40px_rgb(0_0_0/0.9)]">
<svg viewBox="0 0 120 120" aria-hidden="true" class="size-full -rotate-90 drop-shadow-[0_0_16px_rgb(56_217_200/0.3)] motion-reduce:drop-shadow-none">
<circle cx="60" cy="60" r="52" pathLength="100" class="fill-none stroke-tcd04-rule [stroke-width:9]"/>
<circle cx="60" cy="60" r="52" pathLength="100" class="fill-none stroke-tcd04-calm [stroke-dasharray:100] [stroke-linecap:round] [stroke-width:9] animate-tcd04-ring"/>
</svg>
<div class="absolute inset-0 grid place-content-center gap-1">
<p aria-hidden="true" class="flex items-baseline justify-center gap-px font-tcd04-display text-[clamp(44px,13vw,68px)] font-medium leading-none tabular-nums">
<em class="not-italic animate-tcd04-m [counter-reset:tcd04m_var(--tcd04-m)] before:content-[counter(tcd04m)]"></em><span class="opacity-45">:</span><em class="not-italic animate-tcd04-st [counter-reset:tcd04st_var(--tcd04-st)] before:content-[counter(tcd04st)]"></em><em class="not-italic animate-tcd04-su [counter-reset:tcd04su_var(--tcd04-su)] before:content-[counter(tcd04su)]"></em>
</p>
<p class="text-[10.5px] uppercase tracking-[0.24em] text-tcd04-mut">remaining</p>
</div>
<div role="status" class="invisible absolute inset-0 grid place-content-center gap-1 rounded-full bg-tcd04-panel opacity-0 animate-tcd04-done">
<strong class="font-tcd04-display text-[clamp(22px,6vw,30px)] font-medium uppercase tracking-[0.05em] text-tcd04-calm">Connecting</strong>
<span class="text-xs text-tcd04-mut">Agent picking up</span>
</div>
</div>
<ul class="flex flex-wrap justify-center gap-2">
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-lc motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-calm"></span>Plenty of time · above 50%</li>
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-lw motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-warn"></span>Wrap up · under 50%</li>
<li class="flex min-w-0 items-center gap-2 rounded-full border border-tcd04-rule bg-tcd04-panel px-3 py-1.5 text-xs text-tcd04-ink animate-tcd04-ld motion-reduce:animate-none"><span aria-hidden="true" class="size-2 rounded-full bg-tcd04-danger"></span>Final seconds · under 20%</li>
</ul>
<p class="max-w-[52ch] text-[12.5px] leading-relaxed text-tcd04-mut text-pretty">Time-depleting, not task-progress: the arc shrinks because the window is closing, and the legend lights the live band so the state never depends on colour alone. Pure CSS, so it measures five minutes from render.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@property --tcd04-m {
syntax: '<integer>';
inherits: false;
initial-value: 4;
}
@property --tcd04-st {
syntax: '<integer>';
inherits: false;
initial-value: 5;
}
@property --tcd04-su {
syntax: '<integer>';
inherits: false;
initial-value: 9;
}
@theme {
--color-tcd04-page: #070c1a;
--color-tcd04-page: oklch(0.16 0.04 265);
--color-tcd04-glow: #101a36;
--color-tcd04-glow: oklch(0.24 0.06 265);
--color-tcd04-panel: #0d1428;
--color-tcd04-panel: oklch(0.21 0.05 264);
--color-tcd04-ink: #eaf0ff;
--color-tcd04-ink: oklch(0.95 0.02 265);
--color-tcd04-mut: #95a3c7;
--color-tcd04-mut: oklch(0.73 0.05 268);
--color-tcd04-rule: #1c2745;
--color-tcd04-rule: oklch(0.3 0.05 265);
--color-tcd04-calm: #38d9c8;
--color-tcd04-calm: oklch(0.8 0.13 185);
--color-tcd04-warn: #f2b23c;
--color-tcd04-warn: oklch(0.8 0.15 78);
--color-tcd04-danger: #ff5a63;
--color-tcd04-danger: oklch(0.68 0.2 20);
--font-tcd04-display: 'Oswald', 'Archivo Narrow', sans-serif;
--font-tcd04-sans: 'DM Sans', system-ui, sans-serif;
--animate-tcd04-ring: tcd04-arc 300s linear forwards, tcd04-hue 300s steps(1, end) forwards;
--animate-tcd04-m: tcd04-m 300s steps(5, end) forwards;
--animate-tcd04-st: tcd04-st 60s steps(6, end) infinite;
--animate-tcd04-su: tcd04-su 10s steps(10, end) infinite;
--animate-tcd04-done: tcd04-done 0.01s linear 300s forwards;
--animate-tcd04-lc: tcd04-lc 300s steps(1, end) forwards;
--animate-tcd04-lw: tcd04-lw 300s steps(1, end) forwards;
--animate-tcd04-ld: tcd04-ld 300s steps(1, end) forwards;
@keyframes tcd04-arc {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 100;
}
}
@keyframes tcd04-hue {
0% {
stroke: var(--color-tcd04-calm);
stroke-width: 9;
}
50% {
stroke: var(--color-tcd04-warn);
stroke-width: 9;
}
80%,
100% {
stroke: var(--color-tcd04-danger);
stroke-width: 11;
}
}
@keyframes tcd04-m {
from {
--tcd04-m: 4;
}
to {
--tcd04-m: -1;
}
}
@keyframes tcd04-st {
from {
--tcd04-st: 5;
}
to {
--tcd04-st: -1;
}
}
@keyframes tcd04-su {
from {
--tcd04-su: 9;
}
to {
--tcd04-su: -1;
}
}
@keyframes tcd04-done {
to {
opacity: 1;
visibility: visible;
}
}
@keyframes tcd04-lc {
0% {
opacity: 1;
}
50%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-lw {
0% {
opacity: 0.4;
}
50% {
opacity: 1;
}
80%,
100% {
opacity: 0.4;
}
}
@keyframes tcd04-ld {
0% {
opacity: 0.4;
}
80%,
100% {
opacity: 1;
}
}
}
.tcd-04 {
width: 100%;
min-height: 100svh;
}
```How this works
Percent dashes. pathLength="100" tells SVG to treat the circle's circumference as 100 units. With stroke-dasharray: 100, a stroke-dashoffset of 0 is a full ring and 100 is empty — any radius, no 2πr.
Colour on a step function. A second animation on the same arc uses steps(1, end), so the stroke holds teal until 50%, jumps to amber, then to red (and thickens) at 80% elapsed:
@keyframes tcd04-hue {
0% { stroke: var(--color-tcd04-calm); }
50% { stroke: var(--color-tcd04-warn); }
80% { stroke: var(--color-tcd04-danger); }
}The legend follows. Each chip carries a matching steps(1,end) opacity animation, so the band that is live is the one at full strength — state never depends on ring colour alone.
Digits. Three registered integers drive counters for M:SS; at 300s a delayed panel announces the agent is connecting.
Make it yours
- Change the window: edit
300sin every--animate-tcd04-*token and the minuteinitial-value. - Thicker ring: change
[stroke-width:9]on both circles and the11in the hue keyframe. - Move the band thresholds by editing the
50%/80%stops in all four keyframes together. - Swap the round caps for flat ones by removing
[stroke-linecap:round].
Gotchas — read before shipping
- Rotate the SVG
-rotate-90, not the circle, or the ring starts at 3 o'clock. - Round caps draw a dot even at
dashoffset: 100; the delayed terminal panel covers it. - Pure CSS measures from render — for a hold time owned by a server, drive
--tw-values from script instead. @keyframesin@themeare emitted only when ananimate-*utility references them.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
pathLength and dash animation are universal. Animated integer @property values match Tailwind v4's floor; below it the ring still depletes but the digits stay at their initial values.