# Countdown (/ui/components/react/countdown)



<Playground component="countdown" />

## Usage [#usage]

```tsx
import { Countdown, CountdownSegment } from '@appica/ui-react/countdown'
```

```tsx
<Countdown targetDate="2027-01-01T00:00:00Z">
  <CountdownSegment unit="days" /> <span>days</span>
  <CountdownSegment unit="hours" /> <span>hours</span>
  <CountdownSegment unit="minutes" /> <span>minutes</span>
  <CountdownSegment unit="seconds" /> <span>seconds</span>
</Countdown>
```

`Countdown` is the timing engine: give it a `targetDate` (a `Date`, epoch-ms number, or date
string) or a relative `duration` in seconds, and it ticks down once per second, breaking the
remaining time into `days` / `hours` / `minutes` / `seconds`. Each `CountdownSegment` reads one
of those units from context and renders it as **rolling digits** — every digit is a `0–9` strip
moved with a GPU `transform`, so changes slide like an odometer without thrashing layout.

It's intentionally **headless**: the root is just an `inline-flex` wrapper and the segments add no
styling of their own — they inherit the font, size, and color from your markup. There are **no
built-in labels** — you compose "Days/Hours/…" text (and any separators or boxes) yourself, styling
everything through `className`.

### Time zones [#time-zones]

`Countdown` has no `timeZone` prop, and it doesn't need one. The remaining time is `target − now`,
a subtraction of two **absolute instants** (both UTC epoch-milliseconds under the hood), so the
result is a duration — and a duration is the same everywhere. "3 hours left" is 3 hours in every
zone at once, so there is nothing for a time zone to change.

Time zones only matter when you decide *which instant* `targetDate` is — and you do that when you
construct it, before the component sees it. Pass an offset-bearing string or a real `Date`, and the
countdown is identical for every viewer:

```tsx
<Countdown targetDate="2027-01-01T00:00:00+09:00" /> // midnight in Tokyo, for everyone
<Countdown targetDate="2027-01-01T00:00:00Z" /> // midnight UTC
<Countdown targetDate={new Date(Date.UTC(2027, 0, 1))} /> // also an absolute instant
```

<Callout type="warning" title="Avoid zone-less local strings">
  A string without an offset — `"2027-01-01T00:00:00"` — is parsed in **each viewer's own** time zone, so it points at a
  different instant for everyone. Always include an offset (`+09:00`) or a trailing `Z`, or compute the instant with a
  `date-fns` time-zone helper and pass the resulting `Date`.
</Callout>

## Examples [#examples]

### Labelled countdown [#labelled-countdown]

Wrap each `CountdownSegment` with your own label and size it however you like. The digits stay
aligned because the segment uses tabular figures.

```tsx
import { Countdown, CountdownSegment } from '@appica/ui-react/countdown'

type Unit = 'days' | 'hours' | 'minutes' | 'seconds'

function Field({ unit, label }: { unit: Unit; label: string }) {
  return (
    <div className="flex flex-col items-center">
      <CountdownSegment unit={unit} className="text-foreground-intense text-4xl font-semibold" />
      <span className="text-foreground-muted mt-1 text-sm">{label}</span>
    </div>
  )
}

export default function CountdownDefault() {
  return (
    <Countdown targetDate="2027-01-01T00:00:00Z" className="gap-6" aria-label="Time until 2027">
      <Field unit="days" label="Days" />
      <Field unit="hours" label="Hours" />
      <Field unit="minutes" label="Minutes" />
      <Field unit="seconds" label="Seconds" />
    </Countdown>
  )
}
```

### Relative duration [#relative-duration]

Pass `duration` (seconds from mount) instead of `targetDate` for timers like a flash sale, and use
`onComplete` to react when it hits zero.

```tsx
'use client'

import { useState } from 'react'
import { Button } from '@appica/ui-react/button'
import { Countdown, CountdownSegment } from '@appica/ui-react/countdown'

export default function CountdownDuration() {
  const [done, setDone] = useState(false)

  if (done) {
    return (
      <div className="flex flex-col items-center gap-3">
        <p className="text-foreground-intense text-lg font-semibold">Time&apos;s up! 🎉</p>
        <Button variant="outline" size="sm" onClick={() => setDone(false)}>
          Restart
        </Button>
      </div>
    )
  }

  return (
    <Countdown
      duration={20}
      onComplete={() => setDone(true)}
      className="text-foreground-intense gap-1 text-3xl font-semibold"
      aria-label="Offer ends in"
    >
      <CountdownSegment unit="minutes" />
      <span className="text-foreground-muted">:</span>
      <CountdownSegment unit="seconds" />
    </Countdown>
  )
}
```

### Custom styling [#custom-styling]

Because every part takes a `className` (merged via `tailwind-merge`), you can box each segment, add
separators, or restyle the digits entirely.

```tsx
import { Countdown, CountdownSegment } from '@appica/ui-react/countdown'

const box =
  'bg-background-subtle text-foreground-intense rounded-lg border px-3 py-3.5 font-mono text-2xl font-semibold'

export default function CountdownStyled() {
  return (
    <Countdown targetDate="2027-01-01T00:00:00Z" className="gap-2" aria-label="Time remaining">
      <CountdownSegment unit="hours" className={box} />
      <span className="text-foreground-muted self-center text-2xl font-semibold">:</span>
      <CountdownSegment unit="minutes" className={box} />
      <span className="text-foreground-muted self-center text-2xl font-semibold">:</span>
      <CountdownSegment unit="seconds" className={box} />
    </Countdown>
  )
}
```

### Standalone rolling number [#standalone-rolling-number]

`CountdownSegment` also works on its own — pass a `value` instead of a `unit` to animate any number
you drive yourself, no timer required.

```tsx
'use client'

import { useState } from 'react'
import { Button } from '@appica/ui-react/button'
import { CountdownSegment } from '@appica/ui-react/countdown'

export default function CountdownValue() {
  const [value, setValue] = useState(8)

  return (
    <div className="flex items-center gap-5">
      <CountdownSegment value={value} className="text-foreground-intense text-5xl font-semibold" />
      <div className="flex gap-2">
        <Button variant="outline" size="sm" onClick={() => setValue((v) => Math.max(0, v - 1))}>
          -1
        </Button>
        <Button variant="outline" size="sm" onClick={() => setValue((v) => v + 1)}>
          +1
        </Button>
        <Button variant="outline" size="sm" onClick={() => setValue((v) => v + 10)}>
          +10
        </Button>
      </div>
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a
container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your
tree in `DirectionProvider` so direction-aware behavior follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The segments reorder to follow the resolved direction, while the digits themselves keep rolling
vertically — numbers read the same in any direction. For setup details and caveats, see the
[RTL guide](/ui/docs/react/rtl).

<RtlPreview component="countdown" />

## API reference [#api-reference]

### Countdown [#countdown]

The timing engine and headless container. Renders a `<div role="timer">` and provides the remaining
time to its `CountdownSegment` children.

| Prop         | <ColMinWidth width="200">Type</ColMinWidth>      | Default | <ColMinWidth width="220">Description</ColMinWidth>                                                      |
| ------------ | ------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------- |
| `targetDate` | <Code>Date \| number \| string</Code>            | —       | Absolute instant to count down to (a `Date`, epoch-ms, or date string).                                 |
| `duration`   | `number`                                         | —       | Relative length in **seconds** from mount. Ignored when `targetDate` is set.                            |
| `interval`   | `number`                                         | `1000`  | Tick interval in milliseconds.                                                                          |
| `onComplete` | <Code>() => void</Code>                          | —       | Fired once when the countdown reaches zero.                                                             |
| `children`   | <Code>ReactNode \| ((parts) => ReactNode)</Code> | —       | Segments and labels, or a render-prop receiving `{ days, hours, minutes, seconds, total, isComplete }`. |

Forwards `ref`, `className`, `aria-*`, and any other `<div>` attributes. Use `aria-label` to name the
timer (e.g. "Offer ends in").

### CountdownSegment [#countdownsegment]

Renders one unit as rolling digits. Use it inside a `Countdown` with `unit`, or standalone with `value`.

| Prop        | <ColMinWidth width="200">Type</ColMinWidth>              | Default | <ColMinWidth width="220">Description</ColMinWidth>                           |
| ----------- | -------------------------------------------------------- | ------- | ---------------------------------------------------------------------------- |
| `unit`      | <Code>'days' \| 'hours' \| 'minutes' \| 'seconds'</Code> | —       | Which unit to read from the parent `Countdown`.                              |
| `value`     | `number`                                                 | —       | Render this number directly instead of reading context (standalone display). |
| `minDigits` | `number`                                                 | `2`     | Minimum digit count; the value is zero-padded to this width.                 |

Forwards `ref`, `className`, and any other `<span>` attributes. The digit rollers honour
`prefers-reduced-motion` (snapping instantly instead of sliding).

## Accessibility [#accessibility]

* The root renders `role="timer"`; give it an `aria-label` describing what it counts down to.
* The animated digits are `aria-hidden`; each segment also renders an `sr-only` plain-text value so
  screen readers announce the number (with your adjacent label text).
* The timer does **not** announce on every tick by default (no `aria-live`), avoiding once-a-second
  spam. Add `aria-live="polite"` to the root yourself if live updates are appropriate.
* Digit rolling honours `prefers-reduced-motion` — digits update instantly without the slide.
