Countdown
SM · 950B gzip budgetA live countdown with locale-correct digits.
- Category
- Feedback & status
- Budget
- sm, single-purpose control
- External runtime
- React / React DOM only
axe fixtureSSR rendersource scanbundle budget
Preview
Live fixture01:00
Try the component here. Locale-aware formatting and direction follow the selected language; example content and labels are not automatically translated. Overlay demos use interactive launchers.
Install
npx shadcn@latest add https://gear5-ui.vercel.app/r/countdown.jsonCopies the source into your project. Pulls in 2 shared registry items: use-locale, locale.
New here? Set up Tailwind and import aliases first →Source & props
Edit on GitHub ↗Prop types and inline documentation are included below. This is repository source; the installer rewrites shared imports for your project.
"use client";
import { useEffect, useState } from "react";
import { useLocale } from "../lib/use-locale";
export interface CountdownProps {
target: Date | number;
/** Called once when the countdown reaches zero. */
onComplete?: () => void;
className?: string;
}
function remaining(target: Date | number): number {
return Math.max(0, new Date(target).getTime() - Date.now());
}
/**
* A live countdown with digits formatted through `Intl.NumberFormat`, so a
* reader using Arabic-Indic or Devanagari numerals sees their own digits, not
* Western ones dropped into an otherwise-translated page. Updates once a
* second — no animation, so there is nothing here for reduced-motion to
* disable.
*/
export function Countdown({ target, onComplete, className }: CountdownProps) {
const { locale } = useLocale();
const [ms, setMs] = useState(() => remaining(target));
useEffect(() => {
if (ms <= 0) {
onComplete?.();
return;
}
const timer = setInterval(() => setMs(remaining(target)), 1000);
return () => clearInterval(timer);
}, [target, ms, onComplete]);
const totalSeconds = Math.ceil(ms / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const format = (n: number) => new Intl.NumberFormat(locale, { minimumIntegerDigits: 2 }).format(n);
return (
<span role="timer" aria-live="off" className={className}>
{hours > 0 && `${format(hours)}:`}
{format(minutes)}:{format(seconds)}
</span>
);
}
What CI checks
Quality contract- Bundled, minified and gzipped against its tier budget
- Audited by axe in the state previewed above
- Rendered through react-dom/server with no browser globals
- Scanned for network calls, dangerous sinks, and unguarded animation