Rolling Number
Odometer-style number wheels — when value changes, each digit springs vertically to its new value; added digits fade in and removed digits collapse.
Loading preview…
npx shadcn@latest add https://webberui.com/r/rolling-number.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
12345
0
<RollingNumber />
Installation
npx shadcn@latest add https://webberui.com/r/rolling-number.jsonOr, once registries are configured in components.json, install it as @webberui/rolling-number.
Usage
import { RollingNumber } from "@/components/ui/rolling-number";
const [price, setPrice] = React.useState(1280);
<RollingNumber value={price} prefix="$" className="text-5xl font-bold" />Just update value and every digit rolls to its new position. Font size, weight, and color are all controlled through className; internally the wheel height is computed in em, so it stays aligned at any font size.
With decimals and a suffix:
<RollingNumber value={98.6} decimals={1} suffix="%" />Props
RollingNumber
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | The number to display; when it changes, every digit rolls to its new value |
decimals | number | 0 | Number of decimal places |
prefix | string | "" | Prefix before the number (for example $) |
suffix | string | "" | Suffix after the number (for example + or %) |
locale | string | "en-US" | Intl.NumberFormat locale, which decides the thousands and decimal separators |
springConfig | SpringOptions | { stiffness: 300, damping: 30, mass: 0.8 } | Spring parameters for the wheels |
className | string | — | Appended to the outermost <span> className |
How it works
- How it differs from Animated Stats: Animated Stats is an integer count-up — a single MotionValue tweens from 0 to the target and the whole string is rewritten frame by frame. Rolling Number is odometer-style — each digit is its own vertical wheel (a column of 0–9 stacked vertically), and when value changes each wheel rolls to its own target digit. The former suits statistics that animate once on reveal; the latter suits live values that change repeatedly (prices, stock levels, counters)
- Wheel mechanics: each digit column is
1emtall withoverflow: hidden, containing 0–9 stacked vertically at1emeach; a spring drivestranslateY = -digit * 1emto roll to the target cell - Stagger: each digit is delayed by 0.03 seconds going from the lowest place upward (the ones digit rolls first), producing the linked feel of a mechanical gauge
- Adding and removing digits: character keys are laid out by position counted from the right, so the ones place is always the same wheel. When digits are added, the new place (and the new thousands comma) fades in from the left and expands its width; when digits are removed, the old place collapses its width and fades out (
AnimatePresenceplus a width animation) - Separator characters: the thousands comma and the decimal point come from
Intl.NumberFormatand are static characters that do not roll.tabular-numsis applied throughout, so digits are monospaced and do not jitter while rolling
Accessibility
- When the user has "reduce motion" enabled at the system level, the wheels and digit transitions are skipped and the new value is shown as static text
- The container carries an
aria-label(the fully formatted value, including prefix and suffix) and the wheel layer is hidden from assistive technology (aria-hidden), so the screen reader announces only the current complete value and never reads the 0–9 inside the wheels