Number flow
Free · MITDigits roll to the new value with locale formatting, prefixes and suffixes.
Installation
$ npx shadcn@latest add https://goodui.dev/r/number-flow.jsonThe file lands in components/goodui/number-flow.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/number-flow works too.
Usage
import { NumberFlow } from "@/components/goodui/number-flow"
export function PlanPrice({ yearly }: { yearly: boolean }) {
return (
<div className="flex items-baseline gap-1">
<NumberFlow
value={yearly ? 39 : 49}
format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
className="text-4xl font-semibold tracking-tight"
/>
<span className="text-sm text-muted-foreground">per seat / month</span>
</div>
)
}When to use
- A live value that changes while the user watches: revenue on a dashboard, a cart total, seats left.
- Prices that switch with a monthly/yearly toggle, so the change is visible instead of a jump.
- Small counts next to controls (+ / - steppers) where each change should feel physical.
- A number that should count up once when it scrolls into view: use counter-up.
- Showing the change itself (+12.4%) rather than the value: use delta-badge.
- Values in a dense table that update often: use cell-flash, which signals the direction without moving the layout.
Examples
Dashboard revenue in another locale
<NumberFlow
value={48295.5}
locale="de-DE"
format={{ style: "currency", currency: "EUR" }}
className="text-3xl font-semibold"
/>Percent with prefix and suffix
<NumberFlow value={0.684} format={{ style: "percent", maximumFractionDigits: 1 }} />
<NumberFlow value={1284} prefix="~" suffix=" bookings" />API reference
| Prop | Type | Default |
|---|---|---|
| value* Number to show. Changing it rolls each digit to its new value. | number | — |
| format Intl.NumberFormat options, e.g. { style: "currency", currency: "USD" }. | Intl.NumberFormatOptions | — |
| locale Locale passed to Intl.NumberFormat ("de-DE" gives 1.234,5). Defaults to the GooduiLocaleProvider language, else en-US. | string | — |
| prefix Text placed before the formatted number, e.g. "~". | string | "" |
| suffix Text placed after the formatted number, e.g. " / mo". | string | "" |
| className Classes merged onto the root element. | string | — |
Accessibility
- The formatted value is rendered once in an sr-only span; the rolling digit columns are aria-hidden, so screen readers never read ten digits per column.
- There is no aria-live region: wrap it in your own live region if a change must be announced.
Motion
- Each digit is a 0-9 column that slides on a spring (stiffness 140, damping 20, mass 0.8). A 9 going to 0 rolls back down the column through every digit, it does not wrap.
- Separators, currency symbols, prefix and suffix swap instantly; only digits roll.
- Characters are keyed by their distance from the end, so units stay in their column when the number gains or loses digits.
- Reduced motion: the transition becomes duration 0, so digits snap to the new value.
- Each digit is one motion span animating transform only. There are no timers; nothing runs while the value is stable.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Pass a number to value and formatting through format (Intl.NumberFormatOptions) and locale; do not pre-format it into a string.
- For currency use format={{ style: "currency", currency: "USD" }}, not prefix="$".
- It animates on value change only; it does not count up on mount. Use counter-up for that.
- Give the font size and weight through className; the component sets tabular-nums itself.
- There is no labels prop: the only text it renders is the formatted value.
FAQ
Does it animate on first render?
No. initial is false, so it shows the value immediately and only rolls when value changes. Use counter-up for a count from zero.
Can it show decimals and currencies?
Yes. Anything Intl.NumberFormat formats works: currency, percent, compact notation, fixed fraction digits, any locale. Each digit in the output rolls.
Related
Counts from 0 (or from) to the value with an ease-out curve when it scrolls into view, with locale formatting, once or replay-on-return, and counts from the current value when it changes.
"+12.4%" change badge whose digits roll, arrow rotates to the new direction and background pulses green or red, with an inverted mode for metrics where down is good.
Wraps a table value and briefly flashes it green when it rises, red when it falls, with a fading ▲/▼, like a trading terminal.
KPI card with seven variants, tones, shadows and sizes: count-up or rolling NumberFlow values, auto-toned delta badges (invertible for no-show rates), line, area or bar sparklines that draw in, icons, footers and a skeleton state.