Skip to the page
Tactile UI

Animated number counters for the web in 2026, compared: odometer.js and its alternatives

I make a UI library with an odometer in it, so read this knowing that. I went through the animated number counters people actually install and kept the 10 worth knowing about, plus mine, with the rest in a short list at the end. Everything here was checked on 12 October 2026 against each project's source, README, docs and npm page. I read the code of all of them except the paid one, whose source is private. For the question that matters most to an odometer, what the other digits do when 1,099 becomes 1,100, I also ran three of them (odometer.js, NumberFlow and mine) in headless Chromium and timed every digit. I didn't try any of them in Safari, Firefox or a screen reader. Download counts are npm's, for the week of 5 to 11 October. If something here is wrong, email me and I'll fix it.

What actually differs

Nearly every counter animates a number when it changes. These are the things that don't come standard:

  • Ticking or rolling. A text counter prints a new number on every frame, so you see 1,043, 1,044, 1,046 and so on. A rolling counter slides strips of digits behind a window. Text counters are smaller and simpler. Rolling ones look like the object.
  • Carry. On a real odometer a drum only turns while the one to its right goes from 9 to 0, so 1,099 to 1,100 rolls the last three drums together, right at the end of the units' turn. The table below uses these names. *Carry*: higher digits wait for the one below. *Lockstep*: every digit that changes starts on the same frame, runs for the same time and stops together. *Re-spin*: each column scrolls through other digits, often random ones, before it lands.
  • Screen readers. Does the page get one number, or the digits of every frame? Is a change announced at all?
  • Reduced motion. Does it honour prefers-reduced-motion? Four of the ten I could check do.
  • Formatting. Intl.NumberFormat, a format string of its own, or nothing.
  • Upkeep and licence. The best known odometer was archived years ago, two popular packages have no licence file, and the one paid option is a one-off purchase.

At a glance

CounterStackHow digits moveScreen readersReduced motionLast releaseDownloads
countUp.jsPlain JS; React and Vue wrappersText, rewritten each frameReal text, no ARIANoJul 2026745,298 (react-countup 621,893)
odometer.jsPlain JS and CSSLockstepNothing built in; digits jumble mid-rollNoFeb 2017, archived39,323 (react-odometerjs 18,676)
NumberFlowWeb component; React, Vue, SvelteLockstepOne label on a wrapperYes, by defaultJul 20262,184,710 (React 2,049,478)
Motion AnimateNumberReactNot checkedNot statedNot statedPaidn/a
Rolling NumberPlain JS, React, SolidNot timedOne value, glyphs hiddenYesSep 20267,343
react-slot-counterReactRe-spin, changed columns onlyDummy digits hidden, real digits are textNoApr 202631,067
react-animated-numbersReactRe-spin, every column40 digits per column left readableNoJun 20256,581
react-animated-counterReactStraight along the stripTen digits per column readableYes, shorter tweenApr 20265,572
react-flip-numbersReact3D flip per digitOne label, drums hiddenNoDec 202414,897
Magic UI Number TickerReact, copied inText, springReal text, no ARIANoNot on npmn/a
Tactile UI Odometer (mine)Plain JS and CSS, custom elementCarryReal text, announced once it landsYesOct 202623

Only odometer.js, NumberFlow and mine were run in a browser. The rest of the table is from source and docs.

Text counters

  • countUp.js (MIT), with react-countup and vue-countup-v3 around it. The most installed counter after NumberFlow: 745,298 a week, and react-countup's 621,893 comes on top because it depends on it, so don't add them. Active (2.10.1 in July). It's among the smallest here at 2.2 KB gzipped, counts down as well as up, has "smart easing" for large jumps, and formats with separators, decimals, prefix and suffix, a numerals array or your own function. It has no ARIA and no reduced-motion handling, and it rewrites the text about 60 times a second, so if you put aria-live on it, a screen reader would be offered every frame. react-countup's last release was March 2024. vue-countup-v3 says MIT in its package.json but ships no licence file.
  • Magic UI Number Ticker (MIT, React). A copy-in component, about 50 lines on top of motion: a spring that rewrites the text, up or down. The formatter is hard-coded to en-US, and there's no ARIA and no reduced-motion code. Fine for a stat that counts up once, easy to change since it's your file.

Rolling counters

  • odometer.js (MIT, HubSpot). The original web odometer, with 7,288 stars. It's archived, the last release and commit were on 5 February 2017, and 39,323 people still installed it in the week of 5 to 11 October (about 46,000 in each of the two weeks before). Its docs say the library and largest theme are under 3 KB compressed; the 0.4.8 build I measured is 3.5 KB for the script and about 5 KB with its biggest theme. It has seven CSS themes, no dependencies, and works from plain HTML: add the class odometer to a number and change its text. Catches: all the changing columns slide on one CSS transition (timed below), there's no ARIA or live region, mid-roll its text holds every frame of every column, and it ignores reduced motion. Formatting is a pattern like (.ddd),dd, not Intl. react-odometerjs (3.1.3, 2024) wraps it and needs a client-only import in Next.js. vue-odometer is Vue 2 and from 2018.
  • NumberFlow (MIT, by Maxwell Barvian). The one most people reach for now: 2,184,710 downloads a week for the core and 2,049,478 for the React package, nearly three times countUp.js. A web component with React, Vue and Svelte packages, pre-1.0 (0.6.2, July). It uses the full Intl.NumberFormat, so currency, compact and percent work, respects reduced motion by default, can render on the server, and lets you set the direction of the digits or fix a clock's digits so 59 goes to 00. Catches: changed digits move in lockstep, not as a carry. Screen readers get a label on a wrapper (role="img", updated once per change), but there's no live region, so a change isn't announced unless you add one. Its docs say scientific notation, non-Latin digits and right-to-left locales aren't supported yet. It brings one tiny dependency.
  • Motion AnimateNumber (React). Part of Motion+, which is paid; Motion itself is MIT. The docs put it at 2.5 KB on top of Motion, with Intl.NumberFormat formatting and Motion's spring and tween transitions. Motion+ Solo is a one-time purchase: the page showed £249 when I loaded it from the UK, and its page data lists $399. The source is private, so I couldn't read how it moves or whether it honours reduced motion by itself. Its earlier public package, motion-number (26,353 a week), has no licence field.
  • Rolling Number (MIT, @kitlangton/rolling-number). New (the repo is from 4 September) and thorough: plain DOM, React and Solid, native animation (its README says no per-frame script), Intl formatting, reduced motion respected, direction that follows the displayed magnitude, and one formatted value for screen readers with the glyphs hidden. It leaves the live region to you. It rolls only standard Latin-digit formats and says so. 8.7 KB gzipped, 7,343 downloads a week, version 0.4.1. I didn't time its carry; its README says large jumps have bounded travel and doesn't claim one.
  • react-slot-counter. A slot machine: changed columns scroll through random digits and land, staggered by up to a tenth of a second, with a mode that uses the real digits instead. 31,067 a week, active. Counts down. No formatting of its own (you pass it a string), no reduced-motion code. The dummy digits are hidden from screen readers. It says MIT in package.json and has no licence file.
  • react-animated-numbers (MIT). Every column scrolls through 40 random digits on any change, including the ones that didn't change, and all 40 are left in the page as text. It drops a negative sign, only scrolls one way, and depends on motion. 6,581 a week.
  • react-animated-counter (ISC on npm, MIT on GitHub). A Robinhood-style spring column per digit, with green and red for up and down. It's the one that swaps its spring for a short tween under reduced motion. No ARIA, and no Intl: commas and decimals only.
  • react-flip-numbers (MIT). A 3D flip per digit, 2.3 KB, 14,897 a week. One label on the wrapper and the flipping drums hidden, which is a sound approach. No reduced motion, and no formatting. Last release December 2024.

Mine

  • Tactile UI Odometer (MIT; source, npm tactile-odometer). Ten-sided drums in CSS 3D, driven by one spring on a single number; every drum's position follows from it. Only the last drum is driven. Each drum above turns only while the one below passes 9 to 0, and the first carry takes the last tenth of that turn. The faces are real text, and past 24 digits a second a drum is drawn as a blur instead of strobing. The number is also a visually hidden text element with aria-live="polite", which changes once, when the value lands, and is switched to off while a tally is counting. Under reduced motion it changes instantly. It formats with Intl.NumberFormat (marks, grouping, digit glyphs), takes up to 6 decimals, a prefix and a suffix, and has a tally mode that counts up or down. 17 tests run in Node.

Where it falls short:

  • It's the heaviest here. About 8 KB gzipped for the script, 4.2 KB of CSS and a 20.7 KB numerals font, against 5.7 KB for NumberFlow's core.
  • No framework packages. Plain JS and a custom element; in React or Vue you create it in an effect and destroy it on unmount. Nothing is server-rendered.
  • One look. A mechanical drum in four finishes, 15 digits at most. No currency style: you use a prefix.
  • Young. The package was made on 29 September and it had 23 downloads last week.
  • The rest of my library is paid. This one isn't.

The carry, timed

I set each counter to 1,099, let it settle, then set 1,100, and sampled every digit every frame (about 60 a second). The table gives the time at which each digit had moved 5%, 50% and 95% of its own travel. Defaults everywhere; headless Chromium 156.

1,099 to 1,100 (ms)OnesTensHundreds
odometer.js158 / 624 / 1,474158 / 624 / 1,474158 / 624 / 1,474
NumberFlow58 / 171 / 47358 / 171 / 47358 / 171 / 473
Tactile UI Odometer19 / 117 / 300268 / 300 / 417268 / 300 / 417

In odometer.js and NumberFlow the three digits are indistinguishable: same start, same finish. In mine the ones drum is nearly through before the tens and hundreds begin. In the thousands place none of them moves. odometer.js starts late only because its 2 second ease starts slowly.

On odometer.js, then: the claim that its digits don't carry holds. Each column is built from the leading digits of the number, so every column shows a correct sequence and the end is right, but all of them run on one shared 2 second transition. NumberFlow does the same thing on a 0.9 second one. That's a fair design for a price or a stat, and it's not how the hardware works.

What's missing almost everywhere

  • A carry. Of the nine rolling counters, one is shown to carry, and it's mine. Two move in lockstep, two re-spin their columns, and four I read but didn't time.
  • Announcing the change. NumberFlow and Rolling Number give screen readers one clean value and leave the announcing to you. Mine announces once, when the number lands. react-flip-numbers also gives one label. The rest either rewrite the text constantly (countUp.js, Magic UI), expose every frame of every column (odometer.js) or leave dummy digits readable (react-animated-numbers, react-animated-counter).
  • Reduced motion. Four of the ten I could check respect it: NumberFlow, Rolling Number, react-animated-counter and mine.
  • Other scripts. NumberFlow and Rolling Number both say non-Latin digits and right-to-left layouts aren't supported yet. Mine reads digit glyphs from Intl, but its bundled numerals are Latin only, and I haven't tried another script.
  • Licence files. react-slot-counter and vue-countup-v3 say MIT and ship none. react-animated-counter is ISC on npm and MIT on GitHub.

How I'd choose

  • A number that counts up once on a stat block: countUp.js, or react-countup.
  • Prices and live figures in React, Vue or Svelte, with Intl and reduced motion: NumberFlow.
  • Already paying for Motion+: AnimateNumber, after checking how it handles reduced motion.
  • A slot-machine look in React: react-slot-counter.
  • A mechanical odometer that carries, without a framework: mine, knowing it's heavier and has no wrappers.
  • An old odometer.js: leave it until you next touch that screen, then move.

Also checked

  • tm-odometer: a lightweight odometer library, 481 a week, last release 2025.
  • odometer_countup: a plugin that gives countUp.js an odometer look; 4,705 a week, last release 2023.
  • vue-number-animation (5,037 a week, 2023), animated-number-react (6,627 a week, last commit 2021) and vue3-count-to (3,523 a week, 2021): text counters; I only checked these on npm.
  • ngx-odometer: 2 downloads a week.
  • number-flow-react-native: a React Native port of the NumberFlow idea by another author, 23,609 a week.
  • Ark UI has no animated counter in its component list.