Rolling Number

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless rolling_number
Source View Source

Overview of the Phoenix and LiveView Headless Rolling Number Component

The Mishka Chelekom Headless Rolling Number animates a number to its value in Phoenix and LiveView. It renders one <span> that carries the RollingNumber hook. On mount the hook counts up from 0 to value, and whenever LiveView renders a different value it animates from the number on screen to the new one. The motion uses requestAnimationFrame with an ease-out curve.

The component ships no styling. The span takes class, so size, weight and tabular-nums are yours to choose. duration is the animation time in milliseconds and locale sets the digit grouping. With prefers-reduced-motion: reduce the number is shown at once.

Base UI Rolling Number example - numbers that roll up on mount (Phoenix headless)

The first number uses the default 800 ms. The second takes 1400 ms and is grouped by the browser, so it reads as 1,000,000 in an English locale.

Base UI Rolling Number example - numbers that roll up on mount (Phoenix headless)

20481000000
heex
<div class="flex gap-8 text-neutral-900 dark:text-white">
  <.rolling_number id="baseui-rolling-number-1" value={2048} class="text-3xl font-bold tabular-nums" />
  <.rolling_number
    id="baseui-rolling-number-2"
    value={1_000_000}
    duration={1400}
    class="text-3xl font-bold tabular-nums"
  />
</div>

Updating the value from the server

Assign a new integer and render again. The hook sees the new value in its updated callback and rolls from the current number to it. The element needs a stable id, because LiveView matches the hook to the element by it.

heex
<div class="flex items-center gap-4">
  <.rolling_number id="visitors" value={@visitors} class="text-3xl font-bold tabular-nums" />
  <button type="button" phx-click="randomize">Randomize</button>
</div>
elixir
def handle_event("randomize", _params, socket) do
  {:noreply, assign(socket, :visitors, Enum.random(100..999_999))}
end

Duration and locale

duration defaults to 800. locale is a BCP 47 language tag, such as de-DE, and the hook passes it to the browser's number formatting to group the digits. Without it the browser uses its own locale.

heex
<.rolling_number id="revenue" value={@revenue} locale="de-DE" duration={1200} />

Parts & data attributes

The component has a single part. It carries the structural class chelekom-rolling-number and the values the hook reads.

Partdata-partState / ARIAPurpose
Rootrootdata-value, data-duration, data-localeThe span that carries the RollingNumber hook. Its text is the animated number, and the server renders the plain value as the first text.

Accessibility

There is no formal WAI-ARIA APG pattern for an animated number. The span holds the final number as text from the first render, so with JavaScript off, or before the hook mounts, the number is still on the page. With prefers-reduced-motion: reduce the hook skips the animation and writes the final number straight away.