---
title: Countdown
description: Live countdown to an absolute deadline with overdue and urgent states
visibility: guest
draft: false
---

# Countdown

`<x-wirekit::countdown>` shows the time remaining until an **absolute** deadline,
ticking down every second on the client — no `wire:poll`, no server round-trips.
It colors itself when the deadline is close (urgent) and again once it has passed
(overdue), so a running deadline reads at a glance instead of forcing the reader
to subtract a timestamp from the current time in their head.

Pass an **absolute** instant (a Carbon, an ISO-8601 string, or a unix timestamp)
— never a duration. A duration drifts the moment the tab sleeps or the page is
cached; an absolute instant is recomputed from the real clock on every tick.

## Usage

The largest meaningful unit leads and shrinks down to the smallest — years
appear only once the remaining time reaches a year, so a far-off deadline reads
in years and days instead of tens of thousands of days:

:::preview{title="Countdown to a deadline"}
<x-wirekit::countdown until="2099-12-31 23:59:59" />
:::

## Urgent threshold

Set `warn-threshold` (in seconds) to switch the countdown to its urgent color
before the deadline slips — here, the last hour:

:::preview{title="Urgent in the final hour"}
<x-wirekit::countdown until="2099-12-31 23:59:59" :warn-threshold="3600" />
:::

## Overdue

Once the deadline has passed, the countdown shows its overdue text in the danger
color. Provide your own copy with `expired-text`:

:::preview{title="Overdue deadline"}
<x-wirekit::countdown until="2020-01-01 00:00:00" expired-text="Overdue" />
:::

## Without seconds

For a long deadline, drop the churning seconds with `:show-seconds="false"`:

:::preview{title="Days and hours only"}
<x-wirekit::countdown until="2099-12-31 23:59:59" :show-seconds="false" />
:::

## Choose your units

Pass an explicit ordered subset to `units` — any combination of `years`, `days`,
`hours`, `minutes`, `seconds`. The largest unit you list carries all the overflow
above it (so `units="hours minutes"` shows the total hours), and the smallest
truncates. Large values get locale-aware thousands separators (like
[`price`](/components/price)); pass `locale` to override, or `:separators="false"`
to turn them off:

:::preview{title="Days, hours, minutes only"}
<x-wirekit::countdown until="2099-12-31 23:59:59" units="days hours minutes" locale="en" />
:::

## Segments variant

`variant="segments"` renders each unit as its own boxed block with a label — the
classic dashboard countdown look. When a value changes, it animates (the default
box pulse — see [Change animation](#change-animation) for the styles):

:::preview{title="Boxed segments"}
<x-wirekit::countdown until="2099-12-31 23:59:59" variant="segments" units="years days hours minutes seconds" />
:::

## Change animation

The `segments` variant animates each unit as its value changes, in one of two
styles set with `animate`. It is most visible on the seconds box, which
re-animates every tick. Both styles honor `prefers-reduced-motion` (an instant,
motionless update for users who ask for less motion).

`animate="box"` (the default) pulses the **whole box** — border and background
flash the accent color with a small scale pop:

:::preview{title="Box pulse — the whole box flashes"}
<x-wirekit::countdown until="2099-12-31 23:59:59" variant="segments" units="hours minutes seconds" animate="box" />
:::

`animate="text"` flashes **only the changing number's color** (to your theme's
accent) — the box stays still. It is the understated alternative to the box
pulse; it reads strongest on a theme with a colored accent:

:::preview{title="Text flash — only the number changes color"}
<x-wirekit::countdown until="2099-12-31 23:59:59" variant="segments" units="hours minutes seconds" animate="text" />
:::

Turn motion off entirely with `:animate="false"`:

:::preview{title="Motion off"}
<x-wirekit::countdown until="2099-12-31 23:59:59" variant="segments" units="hours minutes seconds" :animate="false" />
:::

## Livewire

The countdown ticks entirely on the client, so it never triggers a Livewire
round-trip. Bind the deadline itself as a normal prop:

```blade
<x-wirekit::countdown :until="$order->payment_due_at" :warn-threshold="600" />
```

**Completion events.** The countdown is more than display — it notifies when the
deadline passes, so a sibling control can react (re-enable a button, refetch, show a
retry form):

- **`wirekit-countdown-expired`** — a bubbling event dispatched **once** the moment the
  deadline is reached (or immediately for an already-past deadline).
- **`x-modelable="done"`** — a writable boolean that flips to `true` at expiry, so you
  can observe completion with `x-model`.

```blade
{{-- Re-enable "Resend code" only after the retry window elapses --}}
<div x-data="{ ready: false }">
    <x-wirekit::countdown :until="now()->addSeconds($retryAfter)" x-model="ready" />
    <x-wirekit::button x-bind:disabled="!ready" wire:click="resend">Resend code</x-wirekit::button>
</div>
```

The retry-timer pattern pairs naturally with a rate limiter — feed it
`RateLimiter::availableIn()` via `:until="now()->addSeconds($retryAfter)"`.

## Headless: your own copy around the number

Sometimes you want the countdown's clock — the client tick, the deadline
recompute on wake, the completion event — but your **own** wording around the
number ("Resend available in 45 seconds", with your locale's pluralization).
Pass a slot and the countdown becomes headless: it renders your markup instead of
the default units, and your Alpine expressions resolve against its live state.

```blade
<x-wirekit::countdown :until="now()->addSeconds($retryAfter)">
    {{-- `remaining` is the full breakdown plus totals; `expired` / `urgent` /
         `done` are the same states the default display uses. --}}
    <span x-show="!expired" x-text="`Resend available in ${remaining.totalSeconds} seconds`"></span>
    <span x-show="expired">You can resend now.</span>
</x-wirekit::countdown>
```

The reactive state available inside the slot:

| Expression | What it gives you |
|------------|-------------------|
| `remaining` | `{ years, days, hours, minutes, seconds, totalSeconds, totalMs }` — the complete canonical ladder plus totals, independent of the `units` / `variant` props. |
| `expired` | `true` once the deadline has passed. |
| `urgent` | `true` inside the `warn-threshold` window. |
| `done` | The writable completion boolean (also `x-modelable`). |

In headless mode you own the accessible text too — the default screen-reader
`<time>` is not rendered, so add your own (for example a one-time `role="status"`
announcement at zero) to match your app's pattern.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `until` | `Carbon\|string\|int` | `null` | The absolute target instant. A null value renders as immediately overdue. |
| `expiredText` | `string\|null` | `null` | Text shown once the deadline has passed. Defaults to a translatable "Overdue". |
| `warnThreshold` | `int\|null` | `null` | Seconds-remaining threshold below which the countdown turns urgent (warning color). Omit for no urgent state. |
| `units` | `string` | `'auto'` | `'auto'` shows years→seconds and drops leading zero-units, or an explicit ordered subset like `"days hours minutes"`. The largest listed unit carries the overflow. |
| `showSeconds` | `bool` | `true` | Include seconds in `'auto'` mode (ignored when `units` is an explicit list). |
| `variant` | `string` | `'inline'` | `'inline'` (e.g. `73y 190d 12h`) or `'segments'` (boxed blocks with labels). |
| `separators` | `bool` | `true` | Locale-aware thousands separators on large unit values. |
| `locale` | `string\|null` | `null` | BCP-47 locale for the separators. Defaults to the app locale. |
| `animate` | `bool\|string` | `true` | Change animation. For `segments`: `"box"` (default — the whole box pulses with a border + accent flash + scale pop) or `"text"` (only the changing number flashes color). `false` / `"none"` turns it off. The `inline` variant rises and fades when on. Honors `prefers-reduced-motion`. |
| `scope` | `string\|null` | `null` | Scoped personalization key. |

## Accessibility

- The root carries `role="timer"`, which is a live region set to **off** — so the
  ticking value is **not** announced every second.
- The accessible name is the **absolute deadline** (`aria-label`), a stable value
  a screen reader reads on navigation.
- A visually hidden `<time datetime>` holds a coarse "time remaining" phrase in
  the active units (e.g. "2 days, 5 hours"), so assistive tech can read the
  current remaining time on demand without per-second spam.
- The visible ticking units are `aria-hidden` — a decorative convenience over
  the semantic time.

## Keyboard Interaction

This component is purely presentational and does not respond to keyboard input.
