Number ticker
Every digit rolls on its own wheel, odometer style.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/number-ticker.jsonVisitors · live
21,174
Revenue
$48,210.50
components/examples/number-ticker-demo.tsx
"use client";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { NumberTicker } from "@/components/ui/number-ticker";
export default function NumberTickerDemo() {
const [visitors, setVisitors] = React.useState(21174);
React.useEffect(() => {
const t = setInterval(() => setVisitors((v) => v + Math.floor(Math.random() * 7)), 1600);
return () => clearInterval(t);
}, []);
const [revenue, setRevenue] = React.useState(48210.5);
return (
<div className="grid w-full max-w-md gap-4 sm:grid-cols-2">
<div className="rounded-xl border border-border bg-card p-5">
<p className="text-sm text-muted-foreground">Visitors · live</p>
<p className="mt-1 text-3xl font-semibold tracking-tight"><NumberTicker value={visitors} /></p>
</div>
<div className="rounded-xl border border-border bg-card p-5">
<p className="text-sm text-muted-foreground">Revenue</p>
<p className="mt-1 text-3xl font-semibold tracking-tight"><NumberTicker value={revenue} format={{ style: "currency", currency: "USD" }} /></p>
<Button size="sm" variant="secondary" className="mt-3" onClick={() => setRevenue((r) => Math.round((r + 1234.56) * 100) / 100)}>Add sale</Button>
</div>
</div>
);
}When to use
- KPIs and counters where digits rolling makes the change legible, like live visitors or prices.
When not to use
- Values that change many times a second.
- Very long numbers in tight spaces.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/number-ticker.json1Install the dependencies
npm install motion2Copy the source into your project
components/ui/number-ticker.tsx"use client"; import * as React from "react"; import { motion } from "motion/react"; import { cn } from "@/lib/utils"; export interface NumberTickerProps { value: number; /** Any Intl.NumberFormat options, e.g. { style: "currency", currency: "USD" }. */ format?: Intl.NumberFormatOptions; locale?: string; className?: string; } const DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; function Digit({ digit }: { digit: number }) { return ( // overflow: clip (not hidden) keeps the text baseline, so "." and "," line up with digits. <span className="relative inline-block leading-none [overflow:clip]" aria-hidden> <span className="invisible">0</span> <motion.span className="absolute inset-x-0 top-0 flex flex-col items-center" initial={false} animate={{ y: `${-digit}em` }} transition={{ type: "spring", stiffness: 140, damping: 20, mass: 0.8 }} > {DIGITS.map((d) => ( <span key={d} className="block h-[1em] leading-none"> {d} </span> ))} </motion.span> </span> ); } /** Each digit rolls on its own column, odometer style. */ export function NumberTicker({ value, format, locale = "en-US", className }: NumberTickerProps) { const text = React.useMemo(() => new Intl.NumberFormat(locale, format).format(value), [value, format, locale]); // Key characters from the right so the ones digit keeps its column as numbers grow. const chars = text.split("").map((ch, i, all) => ({ ch, key: all.length - i })); return ( <span className={cn("inline-flex items-baseline tabular-nums leading-none", className)}> <span className="sr-only">{text}</span> {chars.map(({ ch, key }) => /\d/.test(ch) ? ( <Digit key={key} digit={Number(ch)} /> ) : ( <span key={`${key}-${ch}`} aria-hidden className="inline-block leading-none"> {ch} </span> ), )} </span> ); }
Usage
Import it from @/components/ui/number-ticker after installing.
example.tsx
import { NumberTicker } from "@/components/ui/number-ticker";
export function Mrr({ value }: { value: number }) {
return <NumberTicker value={value} format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }} className="text-3xl font-semibold" />;
}API reference
NumberTicker
Formats with Intl.NumberFormat and gives each digit its own rolling column.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | – | The number to show. |
| format | Intl.NumberFormatOptions | – | Currency, percent, decimals, compact notation. |
| locale | string | "en-US" | Formatting locale. |
Accessibility
- The formatted value is in a visually hidden span; the digit wheels are aria-hidden.
Motion
- Each digit springs to its new position (stiffness 140, damping 20).
- Columns are keyed from the right, so growing from 999 to 1,000 doesn't shuffle digits.
Responsive behavior
- Inline text; inherits size from its parent.
Performance
- Ten spans per digit; fine for dashboards with dozens of tickers.
Notes for AI
- Pass rounded values; decimals are shown as formatted.
This page as Markdown, for assistants and coding agents:
/components/number-ticker/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt