Mischief

78 / Motion

Number Ticker

Counts to a number rather than replacing it, in whatever currency or format you asked for.

Installs
Uptime
Monthly

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/number-ticker
import { NumberTicker } from "mischief-ui/number-ticker"

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/number-ticker/number-ticker.tsx
"use client" import * as React from "react"import { cn } from "@/lib/utils" export type NumberTickerProps = Omit<  React.HTMLAttributes<HTMLSpanElement>,  "children"> & {  value: number  /** Where it counts from the first time. Defaults to zero. */  from?: number  duration?: number  /** Passed straight to Intl.NumberFormat, so currency and percent work. */

Usage

export function Stat({ revenue }) {
  return (
    <NumberTicker
      value={revenue}
      format={{ style: "currency", currency: "USD" }}
    />
  )
}

What gets read out

The element is labelled with the final value the whole time, and the counting digits are hidden. A reader using a screen reader is told the number, once, rather than being read a blur of intermediate values or catching whatever it happened to be passing through.

It also means the number is correct before the animation starts and correct if it never starts, which is what happens under reduced motion: the value is set straight away.

Counting from wherever it was

When the value changes again the count starts from what was on screen, not from the original starting point. A figure that updates while someone is looking at it moves from the old number to the new one, which is the only reading of it that means anything.

The digits are tabular, so the width does not jump about while it counts.

API

valuenumberWhere it is counting to.
fromnumberWhere it counts from the first time. Defaults to 0.
durationnumberMilliseconds. Defaults to 1400.
formatIntl.NumberFormatOptionsPassed straight through, so currency, percent, and compact all work.
localestringPassed to Intl.NumberFormat.
startOnViewbooleanWaits until it is on screen before counting. On by default.

Accessibility

The final value is the element's label from the first paint, and the animating digits are hidden. Reduced motion sets the number immediately. Nothing here is a live region, so a page of these does not interrupt anybody.