Countdown

The time left until a moment, ticking down to it.

Use it to show how long is left until a fixed moment: a launch, the end of a sale, an auction closing, the wait before a verification code can be sent again. Pass the moment as to, and the component works out what is left and updates once a second.

It reads the clock on every tick instead of subtracting one each second. So it stays correct after the app has been in the background, and it reaches zero at the moment to passes.

Seconds round up. It shows 0:01 through the final second and 0:00 only once the time is up, so a sale never shows zero while it is still running.

For a value that changes for other reasons and should be seen changing, use TextAnimation. For progress towards a total rather than towards a time, use Progress.

Installation

Countdown ships with the library — no separate install.

import { Countdown, Text } from 'panelui-native';

Or copy the source into your project, to own and edit it:

npx panelui-cli@latest add countdown

Usage

const [launch] = useState(() => Date.now() + 2 * 86400 * 1000);

<Countdown to={launch} />

Examples

Inline

variant="inline" writes the time as a line of text, 2d 14:03:22, for a banner, a card header or the line under a field. Put your own words around it in a row.

<View className="flex-row items-baseline gap-1.5">
  <Text size="sm" muted>Ends in</Text>
  <Countdown to={saleEnds} variant="inline" size="sm" />
</View>

Choosing the units

units picks which fields are shown. The largest one takes everything above it, so ['hours', 'minutes'] shows two days as 48 hours. By default, leading units that are zero are dropped, so a countdown three hours out has no days box. The smallest two always stay. Set trim={false} to keep every unit.

<Countdown to={target} units={['hours', 'minutes', 'seconds']} />
<Countdown to={target} trim={false} />

The last few minutes

urgentBelow is a number of seconds. Once that much or less is left, the digits switch to the destructive colour.

<Countdown to={auctionEnds} variant="inline" urgentBelow={300} />

When it reaches zero

onComplete runs once when the time is up. It also runs on mount if to has already passed. Use it to swap the countdown for whatever comes next. onTick receives the seconds left each time the display changes.

const [ready, setReady] = useState(false);
const [target, setTarget] = useState(() => Date.now() + 30_000);

{ready ? (
  <Button variant="outline" onPress={() => { setReady(false); setTarget(Date.now() + 30_000); }}>
    Resend code
  </Button>
) : (
  <Countdown to={target} variant="inline" size="sm" units={['minutes', 'seconds']} onComplete={() => setReady(true)} />
)}

Labels in another language

labels replaces the names under each box in segmented. Pass only the ones you want to change.

<Countdown to={target} labels={{ days: 'Jours', hours: 'Heures', minutes: 'Min', seconds: 'Sec' }} />

Variants

variant

  • segmented (default)
  • inline
<Countdown to={target} variant="segmented" />
<Countdown to={target} variant="inline" />

size

  • sm
  • md (default)
  • lg
<Countdown to={target} size="sm" />
<Countdown to={target} size="md" />
<Countdown to={target} size="lg" />

API Reference

Countdown

PropTypeDefaultDescription
classNamestring—
toDate | number—The moment to count down to, as a Date or epoch milliseconds.
unitsCountdownUnit[]—Which units to show, largest to smallest. The largest one absorbs everything above it — ['hours', 'minutes', 'seconds'] shows two days as 48 hours. Defaults to all four.
trimbooleantrueDrop leading units while they are zero, so a launch three hours away has no "00 days" box. The smallest two always stay.
labelsPartial<Record<CountdownUnit, string>>—Names under each box in segmented. Pass any subset to translate them.
urgentBelownumber—Turn the digits to the destructive colour once this many seconds or fewer are left — the last five minutes of a sale, the last ten seconds of a bid.
onComplete() => void—Called once when the time is up, including on mount if it already is.
onTick(secondsLeft: number) => void—Called with the whole seconds left each time the display changes.

Every part also accepts the underlying React Native props (ViewProps or TextProps) and a className for Tailwind utilities.

Notes

Keep to stable

to is the moment itself, not a duration. Compute it once, in state or from your data, and pass the same value on every render. Date.now() + 30_000 written straight into the JSX makes a new target on every render, so the countdown restarts each time the parent renders.

Accessibility

The countdown is a single element with the timer role. Its label is coarser than the display: it names days, hours and minutes, and counts seconds only in the last minute. A label that changed every second would be read again every second.

Motion

When a digit changes, the old one slides down and out and the new one comes in from above. Only the digits that changed move. Nothing animates on the first render, and nothing animates when reduce motion is on.

Public exports

Values: Countdown

Types: CountdownProps, CountdownUnit

On this page