Countdown
v0.0.10
mix mishka.ui.gen.headless countdown
Overview of the Phoenix and LiveView Headless Countdown Component
The Mishka Chelekom Headless Countdown is a Phoenix function component that shows the time left until a moment and counts down to it. The server renders the correct remaining time on the first paint, so it is right before any JavaScript runs and right again after a LiveView reconnect. The Countdown JS hook then ticks it down every second from an absolute deadline, so a slow render or a background tab does not drift the clock.
The root is a div with role="timer". Each unit is a unit part that holds a digit and, when you ask for them, a label and a separator. The digit shows the value as text and as a --value custom property. class styles the root, and unit_class, digit_class, label_class and separator_class style the parts. The component ships no styling.
Base UI Countdown example - days, hours, minutes and seconds (Phoenix headless)
Base UI Countdown example - days, hours, minutes and seconds (Phoenix headless)
<.countdown
id="baseui-countdown-hero"
seconds={2 * 86_400 + 5 * 3_600 + 42 * 60 + 17}
show_labels
class="flex items-end gap-4 text-neutral-950 dark:text-white"
unit_class="flex flex-col items-center"
digit_class="text-2xl font-medium tabular-nums"
label_class="text-xs text-neutral-500"
/>Base UI Countdown example - clock with separators (Phoenix headless)
Base UI Countdown example - clock with separators (Phoenix headless)
<.countdown
id="baseui-countdown-clock"
seconds={5 * 3_600 + 42 * 60 + 17}
units={~w(hours minutes seconds)}
separator=":"
class="flex items-center text-2xl font-medium tabular-nums text-neutral-950 dark:text-white"
unit_class="flex items-center"
/>Target or duration
Give the countdown either target or seconds. target is the moment to count down to: a DateTime, a NaiveDateTime read as UTC, or an ISO 8601 string with an offset such as "2027-01-01T00:00:00Z". seconds is a duration, and it is turned into a deadline when the component renders, so it counts from that render. Use target for a fixed moment. A missing, invalid or past target shows zeros and marks the root data-complete.
Units, labels and separators
units lists the units to show, largest first, from days, hours, minutes and seconds. It defaults to all four. The largest unit absorbs everything above it, so a countdown of hours, minutes and seconds with two days left reads 48 hours instead of dropping the days. show_labels adds a label to each unit, and labels renames them, for example %{"seconds" => "sec"}. separator puts text between the units, such as ":" for a clock.
The example below starts at two days, five hours, 42 minutes and 17 seconds, but shows only hours, minutes and seconds, with shorter labels. It reads 53 hours, because the hours unit takes the two days above it.
Countdown example - units and labels (Phoenix headless)
<.countdown
id="countdown-units"
seconds={2 * 86_400 + 5 * 3_600 + 42 * 60 + 17}
units={~w(hours minutes seconds)}
show_labels
labels={%{"hours" => "hrs", "minutes" => "min", "seconds" => "sec"}}
class="flex items-end gap-4 text-neutral-950 dark:text-white"
unit_class="flex flex-col items-center"
digit_class="text-2xl font-medium tabular-nums"
label_class="text-xs text-neutral-500"
/>The generated module also exports split/2, which divides a number of seconds across a list of units. Use it to render the same countdown somewhere the component cannot go, such as a page title or an email.
Completion
on_complete names an event that is pushed once, with an empty payload, when a running countdown reaches zero. The root then gets data-complete. A countdown that is already at zero when it renders does not push the event.
<.countdown
id="sale-ends"
target={@sale_ends_at}
on_complete="sale_ended"
show_labels
class="flex gap-4"
/>def handle_event("sale_ended", _params, socket) do
{:noreply, assign(socket, :sale_open?, false)}
endParts & data attributes
The root is marked data-part="root" and carries data-deadline (the deadline in Unix milliseconds), data-units and data-complete. The parts are unit, digit, label and separator, and the unit and its digit carry data-unit with the unit name, so you can style or hide one unit from CSS. The structural classes are chelekom-countdown, chelekom-countdown__unit, chelekom-countdown__digit, chelekom-countdown__label and chelekom-countdown__separator.
Accessibility
The root has role="timer", which is a WAI-ARIA live region role, and aria-live="off" unless announce is set. Each digit has an aria-label such as "2 days" that the hook keeps current. Labels and separators are aria-hidden, because the digit already names its unit. A countdown is not interactive, so it has no keyboard behavior.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id (carries the Countdown hook) |
target
|
any
| — | — | No | A DateTime, NaiveDateTime or ISO8601 string to count down to |
seconds
|
integer
| — | — | No | How many seconds are left, when you have a duration rather than a moment |
units
|
list
|
["days", "hours", "minutes", "seconds"]
| — | No | Which units to show, largest first; the largest one absorbs everything above it |
show_labels
|
boolean
|
false
| — | No | Render a label beside each unit |
labels
|
map
|
%{}
| — | No | — |
separator
|
string
| — | — | No | — |
announce
|
boolean
|
false
| — | No | Announce each tick politely; off by default, because a per-second live region is noise |
on_complete
|
string
| — | — | No | LiveView event pushed once when the countdown reaches zero |
class
|
any
| — | — | No | Extra classes for the root |
unit_class
|
any
| — | — | No | — |
digit_class
|
any
| — | — | No | — |
label_class
|
any
| — | — | No | — |
separator_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
countdown
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless countdown