Counts up to a number when it scrolls into view, with locale-aware formatting for currency, percentages and decimals.
What changed
- 1.0.1 Oct 2, 2026The locale prop now defaults to the I18nProvider locale (still en-US without a provider) instead of a fixed en-US. Adds the i18n helper as a dependency.
import { NumberTicker } from "@/components/ballmac/number-ticker"
export default function NumberTickerDemo() {
return <NumberTicker value={12840} className="text-5xl font-semibold" />
}Installation
$ pnpm dlx shadcn@latest add @ballmac/number-tickerInstall the dependencies.
$ pnpm add motion@^12Add the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/motion-presets @ballmac/i18nCopy the source into your project.
components/ballmac/number-ticker.tsx// Ballmac UI: Number Ticker. https://ui.ballmac.com/components/number-ticker "use client" import * as React from "react" import { animate, useInView } from "motion/react" import { cn } from "@/lib/utils" import { useReducedMotionSafe } from "@/lib/ballmac/motion" import { useLocale } from "@/lib/ballmac/i18n" type NumberTickerProps = Omit<React.ComponentProps<"span">, "children"> & { /** The number to count to. */ value: number /** Where the count starts. Defaults to 0. */ from?: number /** Seconds. Defaults to 1.4. */ duration?: number /** Seconds to wait after the number scrolls into view. */ delay?: number /** Intl.NumberFormat options, for example { style: "currency", currency: "USD" }. */ format?: Intl.NumberFormatOptions /** BCP 47 locale. Fixed (not the browser's) so server and client render the same text. */ locale?: string } function NumberTicker({ value, from = 0, duration = 1.4, delay = 0, format, locale, className, ...props }: NumberTickerProps) { const defaultLocale = useLocale() locale ??= defaultLocale const ref = React.useRef<HTMLSpanElement>(null) const inView = useInView(ref, { once: true, margin: "0px 0px -10% 0px" }) const reduceMotion = useReducedMotionSafe() const formatter = React.useMemo(() => new Intl.NumberFormat(locale, format), [locale, format]) React.useEffect(() => { const node = ref.current if (!node) return if (reduceMotion) { node.textContent = formatter.format(value) return } node.textContent = formatter.format(from) if (!inView) return const controls = animate(from, value, { duration, delay, ease: [0.22, 1, 0.36, 1], onUpdate: (latest) => { const decimals = format?.maximumFractionDigits ?? (Number.isInteger(value) ? 0 : 2) node.textContent = formatter.format(Number(latest.toFixed(decimals))) }, }) return () => controls.stop() }, [inView, reduceMotion, value, from, duration, delay, formatter, format?.maximumFractionDigits]) return ( <span ref={ref} data-slot="number-ticker" className={cn("inline-block tabular-nums tracking-tight", className)} {...props} > {formatter.format(reduceMotion ? value : from)} </span> ) } export { NumberTicker, type NumberTickerProps }Update the import paths to match your project setup.
Usage
import { NumberTicker } from "@/components/ballmac/number-ticker"The full example is in the Code tab above.
Examples
Currency
import { NumberTicker } from "@/components/ballmac/number-ticker"
export default function NumberTickerCurrency() {
return (
<NumberTicker
value={48250.5}
format={{ style: "currency", currency: "USD", maximumFractionDigits: 2 }}
className="text-5xl font-semibold"
/>
)
}API reference
| Prop | Type | Default |
|---|---|---|
value*The number to count to. | number | — |
fromWhere the count starts. Defaults to 0. | number | 0 |
durationSeconds. Defaults to 1.4. | number | 1.4 |
delaySeconds to wait after the number scrolls into view. | number | 0 |
formatIntl.NumberFormat options, for example { style: "currency", currency: "USD" }. | Intl.NumberFormatOptions | — |
localeBCP 47 locale. Fixed (not the browser's) so server and client render the same text. | string | — |
Also accepts the standard attributes of its root element.
Quality scorecard
Every applicable check passes
Measured in Chromium 153.0.8010.12 on October 19, 2026, on version 1.0.1, across 2 examples.
Passed: Accessibility scan
No serious or critical issues in light and dark, on a phone and a desktop (axe-core 4.13.0, WCAG 2.1 AA and best-practice rules).
Passed: Small screens
No sideways scrolling at 360, 390 or 1280 px wide.
Passed: Reduced motion
No endless animation to begin with.
Passed: Right to left
Mirrors correctly in Arabic, Hebrew, Persian and Urdu layouts (0% of elements off the mirror, limit 15%).
Passed: Clean console
No console errors or React hydration errors while loading.
Not applicable: Keyboard
Static content: nothing to focus.
Not applicable: Loading, empty and error
Does not show data or take submissions.
For information: Automated tests
No test file of its own yet.
For information: Footprint
1.0 KB of source (gzip) in 1 file. Installs motion.
Automated checks find a subset of problems and do not replace testing with a screen reader and a keyboard in your own app. What they miss.
Use with AI
Animated number for stats and KPIs. Respects reduced motion by showing the final value immediately. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Number Ticker (@ballmac/number-ticker) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Stat sections on landing pages
- Dashboard KPIs that load in
Not for
- Numbers that update every second (animate a live value instead)
- Prices users need to read instantly
Registry JSON: https://ui.ballmac.com/r/number-ticker.json
Credits
Free to use in personal and commercial projects.
- npm
- motion@^12
Pairs well with
Text Reveal
Reveals a headline or paragraph word by word or character by character with a soft blur and rise, when it scrolls into view or on mount.
Badge
A small pill label in four variants with an optional status dot and success, warning and error tones. Style links with badgeVariants().
Animated Beam
An SVG beam that connects two elements with a curved path and sends a glowing gradient pulse along it. Follows layout changes, pauses off-screen, static under reduced motion.
Animated Grid
A decorative hairline grid background where a few random cells softly light up and fade, like instrument lights. SVG, hydration-safe, static under reduced motion.
Ballmac UI Pro
Need the whole screen, not one piece?
311 premium blocks (dashboards, settings, calendars, mail, shops, onboarding), a private shadcn registry and Figma tokens. Blocks that show data come with loading, empty and error states built in.
Not ready? Try three Pro blocks free
Leave your email and the blocks appear right here, ready to copy. Yours to use in any project.