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:
Urgent threshold
Set warn-threshold (in seconds) to switch the countdown to its urgent color
before the deadline slips — here, the last hour:
Overdue
Once the deadline has passed, the countdown shows its overdue text in the danger
color. Provide your own copy with expired-text:
Without seconds
For a long deadline, drop the churning seconds with :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); pass locale to override, or :separators="false"
to turn them off:
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 for the styles):
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:
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:
Turn motion off entirely with :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:
<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 totrueat expiry, so you can observe completion withx-model.
{{-- 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.
<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.