Skip to main content
WireKit
Copy for LLM

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:

Countdown to a deadline

Urgent threshold

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

Urgent in the final hour

Overdue

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

Overdue deadline

Without seconds

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

Days and hours only

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:

Days, hours, minutes only

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):

Boxed segments

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:

Box pulse — the whole box flashes

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:

Text flash — only the number changes color

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

Motion off

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 to true at expiry, so you can observe completion with x-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.

Was this page helpful?

Thanks — that helps.

Voting requires cookies or local storage. What we store