A React number counter component whose digits roll like an odometer. For stats, dashboards and pricing.
Odometer playground
Install Odometer
1. New project? One command installs the package and wires everything - see Installation:
pnpm dlx zyncat-ui init2. Import the component - it loads its own stylesheet:
import { Odometer } from '@zyncat/ui/odometer';Usage
import { Odometer } from '@zyncat/ui/odometer'; <Odometer value={count} format={(v) => v.toLocaleString('en-US')} />Compose Odometer in your application. No Tailwind or external styling library is required; every value resolves from Zyncat UI's token vocabulary.
Odometer props
Odometer
OdometerStyle
Frequently asked questions
Import Odometer from '@zyncat/ui/odometer' and give it a value: <Odometer value={12480} />. Every time value changes the digit columns roll to the new number, each on its own spring, so there is no animation code to write. Under prefers-reduced-motion it snaps straight to the value instead of rolling.
Pass a format function. format={(v) => v.toLocaleString('en-US')} renders 12,480 with its comma, and format={(v) => String(v).padStart(8, '0')} renders 00012480. Digits in the returned string become rolling columns; everything else - commas, currency symbols, decimal points, units - stays put as a static separator.
Odometer has no scroll-trigger prop: render it with a starting value and set the real value when your own IntersectionObserver fires. The simulation already pauses whenever the element is off-screen or the tab is hidden, so a counter further down the page burns no frames before anyone sees it.
It is a real animation, not a GIF, a Lottie file or an After Effects template. Odometer renders live DOM - one column per digit, each driven by a spring simulation - so it counts to whatever value your app produces at runtime and stays sharp at any font size, which a pre-rendered number counter animation cannot do.
Yes. @zyncat/ui/odometer ships compiled ESM with its 'use client' directive intact, so it works in the Next.js App Router with no transpilePackages config. The formatted value is in the server-rendered HTML for crawlers and screen readers, and the digit columns start rolling once the component hydrates.
react-countup tweens one value from a start to an end over a duration you set. Odometer instead gives every digit column its own spring, so digits land out of sync and blur in proportion to their own velocity, and a value change mid-roll is absorbed rather than restarted. It adds no runtime dependencies - the motion is a small built-in engine, not GSAP or Framer Motion.