22 CSS Countdown Timers
A countdown timer counts DOWN toward a deadline and drives a decision — buy before the sale ends, extend before the session expires, act before the seat closes. These 22 hand-coded patterns cover the accessible <time> reference build, session timeout warnings, OTP and rate-limit cooldowns, auction anti-snipe extension, delivery ETA, exam thresholds, cart expiry, queue position, and the classic flash-sale and flip-clock surfaces. Every timer recomputes from Date.now() each tick so it survives background-tab throttling, guards at zero instead of rendering negative time, and announces via aria-live at thresholds. Scoped under .cdt-NN; drops into React, Vue, Svelte, or Astro.
Related25 CSS Number Counter Animations22 CSS Progress Bars20 CSS Banners & Alerts22 Tailwind CSS Countdown Timers
Course pickPartner: Zero to Mastery
Affiliate linkWant to build production-ready CSS layouts, animations and UI from scratch?
You just browsed 22 countdown timers. Learn the Flexbox, Grid and animation thinking behind them, so the next component starts in your head, not in a copy-paste.
Every ZTM course in one membership: $25/month, paid yearly at $299 · 30‑day money-back guarantee
The CSS Bootcamp. We may earn a commission, at no extra cost to you.
02. Pure CSS Countdown Timer Without JavaScript (opens in a new tab)
03. Session Timeout Warning with Extend Action (opens in a new tab)
04. Circular Depleting Ring Countdown Timer (opens in a new tab)
05. OTP Resend Cooldown Timer Button (opens in a new tab)
06. Rate Limit Cooldown Button with Retry Timer (opens in a new tab)
07. Auction Ending Countdown with Anti Snipe (opens in a new tab)
08. Delivery ETA Countdown with Live Status (opens in a new tab)
09. Product Launch Waitlist Countdown Page (opens in a new tab)
10. Webinar Registration Countdown with Timezone (opens in a new tab)
11. Appointment Reminder Countdown Card (opens in a new tab)
12. Exam Time Remaining with Warning Thresholds (opens in a new tab)
13. Subscription Trial Expiry Countdown Banner (opens in a new tab)
14. Maintenance Window Countdown Status Page (opens in a new tab)
15. Ticket Queue Position and Wait Countdown (opens in a new tab)
16. Daily Streak Reset Countdown Widget (opens in a new tab)
17. Flash Sale Countdown for Product Pages (opens in a new tab)
18. Sticky Announcement Bar Countdown (opens in a new tab)
19. Shopping Cart Expiration Countdown Timer (opens in a new tab)
20. Coming Soon Launch Page Countdown (opens in a new tab)
21. Circular Activity Timer for SaaS and Fitness (opens in a new tab)
22. Retro Flip Clock Countdown Timer (opens in a new tab)
Frequently asked questions
Why does my countdown timer drift or freeze in a background tab?
setInterval(fn, 1000) is a request, not a guarantee. Browsers throttle timers in background tabs — Chrome clamps them to roughly once per minute after a few minutes hidden, and mobile Safari can suspend them entirely — so a timer that increments a counter each tick silently loses minutes. Even in the foreground, each tick carries a few milliseconds of scheduling error that accumulates: an hour-long timer drifts several seconds. The fix is to never count ticks. Store an absolute target timestamp once, then recompute the remaining time from Date.now() on every tick, so any missed or delayed ticks self-correct the moment the tab wakes. Schedule the next tick aligned to the next whole second with setTimeout(tick, 1000 - (Date.now() % 1000)) rather than a fixed 1000ms interval, which keeps the visible digit flipping on the second boundary instead of sliding. Every JavaScript demo in this collection uses that pattern. Pair it with document.visibilitychange if you also need to re-render immediately on tab focus rather than waiting up to a second.How do I make a countdown timer accessible to screen readers?
aria-live="polite" and letting it announce every second. That produces a continuous stream of interruptions that makes the rest of the page unusable with a screen reader — users routinely abandon a page that does it. Announce at meaningful thresholds instead: one hour, ten minutes, one minute, and expiry. Keep the visual digits outside the live region entirely and put a short sentence inside it that only changes when a threshold is crossed. Wrap the deadline in <time datetime="2026-12-31T23:59:59Z"> so the machine-readable target lives in the markup rather than only in script, and put role="timer" on the container so assistive technology knows what the region is. Give the compact digit display a visually-hidden full-sentence label — "2 hours 14 minutes remaining" — because 04:19:22 read aloud character by character is meaningless. Demo 01 is the reference implementation with an annotation panel labelling each attribute and the reasoning.Can you build a countdown timer with only CSS and no JavaScript?
@property --t { syntax: '<integer>'; initial-value: 0; inherits: false } lets you animate an integer, and counter-reset plus content: counter() renders it as visible digits — a genuine countdown with zero script. What CSS cannot do is target an absolute moment in time. The animation starts when the element renders, so it measures a duration from page load, not the distance to a calendar date. That makes it correct for anything anchored to arrival — a quiz clock, a session timer, a five-minute hold — and wrong for anything anchored to a fixed deadline, because a visitor arriving an hour before your sale ends sees the same numbers as one arriving a week early. It also resets on refresh and cannot survive navigation. Demo 02 ships the pattern with that limitation stated in the demo chrome itself rather than buried in documentation, because a pure-CSS countdown that silently lies about a sale deadline is worse than no countdown.What does WCAG 2.2.1 require for session timeouts and timed tests?
role="alertdialog" so it interrupts appropriately, move focus to the primary action, and offer an extend control. Escape should not dismiss it — a timeout warning is a question that has to be answered, and dismissing it silently returns the user to a page that is about to log them out. The criterion carves out real-time exceptions where extending would invalidate the activity: a live auction close and a queue position genuinely cannot be extended per-user, and those demos document the exemption instead of pretending to offer one. Demos 03, 12 and 19 ship extend affordances; 07 and 15 document the exception.How do I show a deadline correctly across timezones?
new Date("2026-09-01 14:00") is parsed as local time in every browser, so the same string resolves to a different absolute moment for every visitor — your Berlin event silently starts at 14:00 in Denver too. Always store and parse the deadline as an ISO 8601 string with an explicit offset or trailing Z, which is unambiguous everywhere. The second is display: showing only the host timezone forces every visitor to do the conversion in their head, and they get it wrong. Read the visitor's zone with Intl.DateTimeFormat().resolvedOptions().timeZone and render the deadline in it as the primary line, with the host timezone as a secondary line so people coordinating across regions can still reconcile. Use Intl.RelativeTimeFormat for the "in 2 days" phrasing rather than hand-rolled string math, because it handles pluralisation and locale rules you would otherwise get wrong. Demo 10 ships all of this for a webinar registration surface.What happens when the countdown reaches zero?
-00:14 is the clearest possible signal that nobody tested past the deadline, and it appears constantly in production because the tick function keeps subtracting after the target passes. Guard before you compute digits: either clamp with Math.max(0, target - Date.now()), or return early into an explicit end state as soon as the remaining time hits zero. The terminal state itself should be meaningful rather than a row of zeroes — "Sale ended", "Session expired", "Arriving any moment". That last one matters for delivery and ETA surfaces, where passing the estimate is normal and showing negative time reads as a system failure rather than a late courier. Also stop the timer: clear the pending setTimeout so it does not keep firing against an expired target, and make sure the cleanup runs on unmount so a single-page app does not accumulate orphaned timers on every route change. Every JavaScript demo here exposes a cleanup function for exactly that reason.How is this different from the number counter and banner collections?
css-number-counter-animations (25 demos) owns numbers that count UP toward a target: stats, KPIs, milestones, odometers, live tickers. If the number is celebrating a total rather than pressuring a deadline, that is the collection you want. css-banners-alerts (20 demos) owns the site-wide banner as a component — cookie consent, maintenance notice, promo bar — where the bar is the subject and any timer inside it is supporting chrome; here the timer is the subject. css-progress-bars (22 demos) owns determinate progress as a value indicator: upload percentage, storage used, form step. A depleting ring in this collection represents time remaining, not task completion, which is why demo 04 is framed as depletion rather than progress.How portable are these timers across React, Vue, Svelte, and Astro?
.cdt-NN root so nothing leaks into a host page. The one thing that needs attention in any framework is cleanup: each timer holds a pending setTimeout that must be cancelled when the component unmounts, or an app accumulates orphaned timers across route changes and eventually fires callbacks against detached DOM. In React the tick logic goes in a useEffect whose cleanup return calls clearTimeout; Vue uses onMounted and onUnmounted; Svelte uses onMount returning a teardown; Angular clears in ngOnDestroy. Each demo exposes its cleanup as a function on the root element so the wiring is a one-liner. In Astro the pure-CSS demos render as static HTML with no island at all, and the JavaScript ones hydrate with client:visible. Tailwind users can lift the rules into @layer components on v3, or declare the same tokens in @theme on v4 so utilities and demo CSS share one source.Related collections
30 CSS Badges
30 hand-coded CSS badges for status indicators, notifications, membership tiers, live-data displays, and SEO / DevOps / financial dashboards — upload progress, typing indicator, transit line status, Core Web Vitals, ECG heartbeat, CI/CD build pipeline, countdown ring, live price ticker, keycap shortcut, wax seal, conference lanyard, and holographic collectibles. Copy-paste HTML and CSS, WCAG 2.2 accessible, MIT licensed.
20 CSS Banners & Alerts
20 hand-coded CSS banner and alert bars for e-commerce storefronts, SaaS dashboards, marketing sites, and compliance-bound products. Covers GDPR cookie consent with a real preferences panel, promo bars with countdown timers, free-shipping threshold progress, app-install smart banners, maintenance and incident status bars, newsletter signup bars, age verification gates, limited-stock urgency banners, live event announcements, browser upgrade notices, geo and currency switchers, sticky top announcement bars, bottom cookie bars, inline form validation alerts, icon-aligned alert banners, left-border accent alerts, diagonal stripe promos, full-width hero banners, animated gradient border alerts, and a text-wrapping laboratory that shows five strategies for the unbreakable string that breaks more banners than anything else. Every bar is scoped under a .ba-NN prefix for no-collision pasting, guards prefers-reduced-motion, carries the correct aria-live and role semantics, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.
25 CSS Blockquotes
25 CSS blockquotes for article pull quotes, testimonial cards, docs callout admonitions, statement banners, and social-post embeds. Covers giant decorative quotation marks, minimalist hairline and classic left borders, animated gradient and glowing neon borders, glassmorphism and aurora backgrounds, brutalist cards, speech bubbles, thick accent rails, highlighted mark text, newspaper multi-column layouts, an expandable read-more disclosure, a scroll-snap testimonial slider, and one-click copy.





















