Skip to content

Countdown

Count down to a launch, a sale or a deadline in days, hours, minutes and seconds.

import { Countdown, CountdownTime } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use Countdown when the moment itself matters: a launch, the end of an offer, a maintenance window closing. Give it the moment as to. It works out the rest in the browser, ticks on the second boundary and lands on zero exactly at to. For a date people read rather than watch, write it out instead, and for time since something happened use relative text such as "5 min ago".

import { Countdown } from '@youj/react'

export function Launch() {
  return <Countdown to="2026-10-07T16:00:00Z" onComplete={() => location.reload()} />
}

to takes a Date, milliseconds since the epoch or an ISO string. Each unit sits in its own tile with its name underneath. The names come from Intl, so they follow the locale ("days", "Tage", "jours"). The digits are tabular, so the row never shifts while it counts. Set the size with a text class, such as className="text-6xl": the tiles and gaps scale with it.

Nothing time-based is rendered on the server. The server and hydration renders show held zeros with data-state="pending", and the component reads the clock only once it's mounted, so server markup and the first client render always match. A static page built days ahead shows the right time as soon as it loads.

Reference

Propsto, onComplete, formatLabel… (4)

Props

Countdown props
PropType
toRequiredDate | number | string

When time runs out: a Date, ms since the epoch, or an ISO string.

–
onComplete() => void

Called once when time runs out, straight after mount if to has already passed.

–
formatLabel(time: CountdownTime) => string

The text read and announced. Default English, e.g. "6 days, 4 hours left", changing at most once a minute.

–
localestring

A BCP 47 locale for the digits and unit names. Default: the YoujProvider locale.

–
labelsPartial<Record<'days' | 'hours' | 'minutes' | 'seconds', ReactNode>>

The caption under each unit. Default: the unit's name from Intl in the locale.

–
pausedboolean

Stop ticking and hold the last reading, e.g. while it is off screen.

default false

false
Other props pass through: extends HTMLAttributes<HTMLDivElement>.

formatLabel receives { days, hours, minutes, seconds, total } and returns the text screen readers hear. Use it to translate that text or to change how coarse it is.

MotionA digit changes, the old digit leaves, several digits change · reduced motion

Motion

Motion tokens
MomentTokens
A digit changesthe new digit rolls down into its slot from above as the old one falls away and fades
duration.slowease.enter

340ms · cubic-bezier(0.16, 1, 0.3, 1)

340mscubic-bezier(0.16, 1, 0.3, 1)
The old digit leavesit drops and fades, a little faster than the new one arrives
duration.baseease.exit

220ms · cubic-bezier(0.4, 0, 1, 1)

220mscubic-bezier(0.4, 0, 1, 1)
Several digits changethey roll right to left, one step apart, as 10:00 turns to 09:59
stagger.tight

24ms step

24ms step

Reduced motion

Digits change in place with no roll.

Each roll is two small Web Animations on transform and opacity, so the component doesn't need the Motion runtime. If a digit changes again before its roll ends, the digit still arriving leaves from where it is instead of jumping.

Reduced motion

Digits change in place with no roll or fade. The time left stays exact to the second.

Accessibilityannouncements

Accessibility

  • The root is a timer. The digit tiles are hidden from assistive technology, and one polite live region carries the time as words.
  • The spoken text is coarse on purpose: "6 days, 4 hours left" while there are days, hours and minutes under a day, minutes under an hour, then "Under a minute left". It changes at most once a minute, so a screen reader isn't interrupted every second.
  • Nothing is announced when the page loads. The region starts announcing after the first reading, and "Time's up" is announced at zero.
  • The default spoken text is English. Pass formatLabel for other languages. The unit names under the tiles already follow the locale.