Installation
pnpm dlx shadcn@latest add https://asciicn.dev/r/ascii-countdown.jsonUsage
import { AsciiCountdown } from "@/components/asciicn/ascii-countdown";
export function Launch() {
return (
<AsciiCountdown
to="2026-12-01T17:00:00Z"
onComplete={() => console.log("launched")}
/>
);
}to takes a Date, an ISO string or epoch milliseconds. The clock shows days, hours, minutes and seconds, each digit drawn from a 5x7 pixel font. At zero it draws doneText in the same type and calls onComplete once.
The countdown fills its container's width. It picks the largest digit that fits, up to size pixels tall, by changing how many cells each font pixel covers. When one row would make the digits smaller than 40 px, as on a phone, it splits into two rows: days and hours, then minutes and seconds.
When a digit changes, only the cells that differ run through a few glyphs and settle, each at its own moment. scramble sets how long that takes. The colons dim and brighten once a second, in step with the seconds.
Examples
Banner
size={22} and 6 px cells keep the digits small enough for a strip at the top of a page. Labels and ghost pixels are off, and only the colons take the accent.
Done state
Five seconds out, with hideZeroDays. At zero the last digits scramble into doneText and onComplete changes the button.
Braille dot matrix
charset="braille" draws each font pixel as a patch of dots. colorMode="duotone" shades each digit from the top of the sodium ramp down. Duotone ramps are tuned for dark paper.
Props
| Prop | Type | Default |
|---|---|---|
| to The moment to count down to: a Date, an ISO string or epoch milliseconds. | Type: Date | string | number | Default – |
| labels Labels under days, hours, minutes and seconds, or false for none. | Type: readonly [string, string, string, string] | false | Default ["DAYS", "HRS", "MIN", "SEC"] |
| doneText Drawn in the same big type once the countdown reaches zero. Letters, digits and ! ? . - /. | Type: string | Default "LIVE" |
| onComplete Called once when the countdown reaches zero, or on mount if to has already passed. | Type: () => void | Default – |
| scramble Seconds a changed digit scrambles before it settles. 0 turns it off. | Type: number | Default 0.45 |
| hideZeroDays Drop the days group once fewer than 24 hours are left. | Type: boolean | Default false |
| charset Glyphs the digits are drawn with. blocks draws half blocks, quadrants and braille split each cell, ramps use their densest glyph. | Type: CharsetName | Default "blocks" |
| chars A custom ramp from empty to dense. Its densest glyph inks the digits. Wins over charset. | Type: string | Default – |
| colorMode mono draws digits in ink and the accent; duotone shades the ink digits top to bottom along ramp. | Type: "mono" | "duotone" | Default "mono" |
| ramp duotone: a ramp name or #rrggbb stops. | Type: RampName | readonly string[] | Default "sodium" |
| accent The accent colour: a palette name, CSS variable name or any CSS colour. | Type: AsciiColor | Default "orange" |
| accentOn Which parts take the accent. | Type: "seconds" | "colons" | "both" | "none" | Default "seconds" |
| ghost Faint dots on the unlit pixels of each digit. | Type: boolean | Default true |
| size Largest digit height in CSS pixels. The digits shrink to fit the width. | Type: number | Default 128 |
| cell Glyph cell height in CSS pixels. Smaller cells fit the width more closely. | Type: number | Default 8 |
| animate false holds still: no scramble or blinking, though the time still ticks. | Type: boolean | Default true |
| fps Frame rate cap. | Type: number | Default 30 |
| paper Background behind the glyphs. | Type: AsciiColor | "transparent" | Default "transparent" |
| fx CRT effects, drawn as a CSS overlay. | Type: PostFx | Default – |
| handle Filled with a handle for export: the latest frame and the canvas. | Type: RefObject<AsciiHandle | null> | Default – |
Other props pass through to the root div.
Notes
- Changed digits scramble for a beat and settle, the colons blink softly, each group has a label under it, and at zero it shows
doneTextand callsonComplete. - The digits are drawn on a canvas that is
aria-hidden. A visually hiddenrole="timer"element gives the time left to the minute and the target date, so a screen reader is not told every second. - Nothing that depends on the clock renders on the server. The first client render matches the server, then the countdown starts, so there is no hydration mismatch.
- With reduced motion, or
animate={false}, digits change without scrambling and the colons hold steady. The time still updates every second. - The canvas pauses offscreen and in hidden tabs. When it comes back, the digits that changed while it was away scramble once.
- Labels are page text under the canvas, so they stay readable at any cell size. They take
--ascii-inkat reduced opacity and are hidden from screen readers, which get the timer text instead.