
Accessible Countdown with Time Element and ARIA
Published
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 & Alerts

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated
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.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.@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.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.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.-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.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..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.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 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 hand-coded CSS blockquote designs for SaaS testimonials, editorial pull quotes, documentation callouts, and developer engineering blogs: large decorative quotation marks with ::before / ::after glyphs, Tailwind CSS blockquote component, pure CSS callout / admonition boxes for Docusaurus / Mintlify / Nextra documentation, responsive center-aligned quote banners, modern minimalist left-border editorial style, animated CSS gradient border, classic left border, brutalist high-contrast, glassmorphism frosted card, glowing neon border, article pull quote with text-wrap, speech bubble chat-style, thick vertical accent rail, customer testimonial cards with avatar and star rating, inline highlighted text, marker highlight underline, floating drop-shadow, aurora gradient background, Twitter / X card style, multi-column newspaper editorial, IDE code-comment style, Markdown-compatible defaults, JavaScript testimonial quote slider, expandable read-more, and one-click copy button. 22 Pure CSS + 3 Light JS (slider, expand, copy). Every design scoped under .bq-NN prefix for no-collision pasting, prefers-reduced-motion guarded per WCAG 2.3.3, WCAG 2.2 AA accessible, framework-agnostic (React, Vue, Svelte, Astro, Next.js, Nuxt, Remix, SvelteKit).