Library / SDK
gaoryrt/number-flip avatar
gaoryrt/number-flip

number-flip: a rolling odometer animation for numbers in TypeScript

🎰 Increase your number with flipping animation

661 stars56 forksTypeScriptMIT

At a glance

What is it?
number-flip is a small TypeScript library that animates a number changing with a mechanical flip, the kind you see on a fuel pump or a departure board. It is one class, one method to drive it later, and a set of options for custom easing, digit systems and separators.
Who is it for?
number-flip fits a dashboard or landing page that needs a number to roll from one value to another rather than snap, and that wants the animation in a few lines of JavaScript with no framework attached. It does not fit a project that needs continuous counting, accessibility fallbacks or a large digit system beyond the default, since the animation is a one-shot transition between two values.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 109 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One class, one constructor, one method

number-flip changes a number with a flipping animation, and the whole API is small enough to read in a minute. The syntax section states it plainly.

js
var flipInstance = new Flip(options)
flipInstance.flipTo(instanceOptions)

The returned Flip instance has a function called flipTo, which takes one instanceOptions object, so you can start the flip whenever you want. That split is the useful part of the design. A constructor that animates immediately covers the common case, where the number changes as a side effect of something else happening, and keeping the instance lets you trigger the change later, which covers the case where the target value is not known at construction time. The class is imported by name.

js
import { Flip } from 'number-flip'

and the library is published to npm as number-flip with the install being a single npm install --save number-flip. There is a live demo on CodePen and two demo GIFs in the repository.

The four ways to use it, from immediate to deferred

The README gives four usage patterns. The first creates an instance and flips immediately, passing a node, a from value and a to value.

js
new Flip({
  node: $('.flip'),
  from: 9527,
  to: 42
})

The second adds a delay, in seconds, so the animation starts later.

js
new Flip({
  node: $('.flip'),
  from: 9527,
  to: 42,
  delay: 1 // second
})

The third creates the instance with only a from value and flips it later, which is the case where the destination is not known yet.

js
const el = new Flip({
  node: $('.flip'),
  from: 9527
})

el.flipTo({to: 42})

The fourth shows how to change the duration, also in seconds.

js
new Flip({
  node: document.querySelector('.flip'),
  from: 9527,
  to: 42,
  duration: 2 // second
})

The node option is documented as an Element object representing the animation container, and the parameter list is explicit that the element must already exist in the DOM when you construct the instance.

Easing, custom digit systems, and a direct versus indirect roll

Three options let you change how the motion feels. The easeFn option takes a custom easing function, and the README links to the easing-js library for more, with a note that the default is easeInOutCubic if you do not specify one. The systemArr option is an array of ten entries representing the content of each decimal rolling system, and the default is the digits zero through nine. That default is worth noticing because it means the animation is a decimal odometer by construction, and the example replaces it with Chinese formal numerals, so the same code animates a different counting system.

js
new Flip({
  node: document.querySelector('.flip'),
  from: 73,
  to: 25,
  duration: 2,
  delay: 1,
  easeFn: function(pos) {
    if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
    return 0.5 * (Math.pow((pos-2),3) + 2);
  },
  // for more easing function, see https://github.com/danro/easing-js/blob/master/easing.js
  systemArr: ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
})

The direct option is the subtle one. It decides whether the number rolls straight to the target or passes through every digit on the way. The README's example is from 0 to 99, where the ones place would pass nine digits if it is direct, and would pass 99 digits in nine rounds for the tens place if it is not. It defaults to true. Turning it off gives the effect of a mechanical counter spinning through, which is slower and reads differently, so it is a design choice rather than a performance setting.

Separators for grouped digits

The separator options let you format large numbers the way they are written in a given language. A separator can be a string, and the simplest case is a comma.

js
new Flip({
  node: $('.flip'),
  from: 95279527,
  separator: ','
})

It can also be an array of strings, with a separateEvery option saying how many digits each separator covers.

js
new Flip({
  node: $('.flip'),
  from: 95279527,
  separator: ['万', '亿', '兆'],
  separateEvery: 4
})

That second form is the Chinese grouping by ten thousands, with separateEvery set to 4, and the separator option defaults to off. There is a third option, separateOnly, a number that defaults to 0, and the README notes that separateEvery will not work if separateOnly has been set. So the grouping options are a small family with one documented conflict, which is the kind of detail you only learn from the parameter list.

The DOM structure is three classes, and the CSS is the animation

The styling section is unusual in a good way. The README documents the HTML structure of a three-digit flip, which is a container with one child per digit position, each holding ten digit elements.

code
.number-flip
    .ctnr.ctnr0
        .digit*10
    .ctnr.ctnr1
        .digit*10
    .ctnr.ctnr2
        .digit*10

The height and width of .number-flip is derived from the height and width of .digit, so you change the size by changing the CSS of .digit rather than of the container. The three classes to style are .number-flip, .ctnr and .digit, and you can rename them in the config with containerClassName, digitClassName and separatorClassName, so a project with its own class naming convention does not have to fight the defaults.

js
new Flip({
  containerClassName: 'c',
  digitClassName: 'd',
  separatorClassName: 's'
})

This is a genuinely important design point. The library does not animate with requestAnimationFrame and inline styles, it builds a DOM structure and lets CSS do the motion, which means the animation is styleable, themeable and accessible to prefers-reduced-motion if you add it, and it also means the visual quality of the result depends entirely on the CSS you ship.

Three build outputs, chosen by the exports map

The build and dev section documents the scripts and the output formats. The dev script runs a Vite dev server for the example directory, dev:test runs one for a test page, and the build script runs tsup to produce the dist directory. The README then explains what the build produces: three formats, selected automatically through the exports field in package.json. The ESM file is number-flip.mjs, meant for import statements in tools such as Framer, Vite and Webpack 5. The CommonJS file is number-flip.cjs, meant for require calls. The IIFE file is index.js, meant for a script tag with a global NumberFlip. And number-flip.d.ts holds the TypeScript declarations.

The package manifest confirms it, with the export condition mapping types to the declaration file, import to the ESM build, require to the CJS build and a default pointing at the IIFE bundle. The manifest is version 1.3.0, is marked sideEffects false, and the dev dependencies are tsup, TypeScript and Vite. The build script in the manifest does some file renaming after tsup runs, moving the global bundle to index.js and the declaration file to number-flip.d.ts, which is how the three output names end up matching the exports map.

A small project with an open browser support question

The scope of the project is narrow and it does not pretend otherwise. The repository root holds the TypeScript source as a single number-flip.ts file, plus a tsup config, a Vite config, an example directory with an index.html, a main.css and a compiled dist, a test directory with a test.js, a PROJECT_INTRO.md and two demo GIFs. The TODO list at the end of the README is worth reading for what it says about the state of things. Two items are checked off, flip with FLIP and the syntax, and one is still open, a browser compatibility list. So the author knows which browsers are supported and has not written it down, and that is the kind of gap you should check yourself if you are shipping this to an audience you do not control. The project is MIT licensed, and the credits section thanks Browserstack for providing cross-browser testing. The last push was on 2026-07-06, the repository is not archived, and there are no GitHub releases, so the npm version is the version to track.

Editorial conclusion

number-flip fits a dashboard or landing page that needs a number to roll from one value to another rather than snap, and that wants the animation in a few lines of JavaScript with no framework attached. It does not fit a project that needs continuous counting, accessibility fallbacks or a large digit system beyond the default, since the animation is a one-shot transition between two values. Verify first that your target numbers fit the positive integer expectation, decide between the direct and non-direct rolling modes before wiring it up, and check the class names if you plan to restyle it, because the default CSS is what gives the flip its look.

Frequently asked questions

How do I install and use number-flip?

Run npm install --save number-flip, import the class with import { Flip } from 'number-flip', and construct a new Flip with a node element, a from value and a to value. The element must already exist in the DOM when you construct the instance.

How do I make the flip start later instead of immediately?

Create the instance with only a from value, keep the returned instance, and call its flipTo method with the target later. The README documents the syntax as new Flip(options) followed by flipInstance.flipTo(instanceOptions), where instanceOptions takes to, duration, easeFn and direct.

What does the direct option do in number-flip?

It decides whether the number rolls straight to the target or passes through each digit. The README's example is from 0 to 99, where the ones place would pass nine digits if it is direct, and would pass 99 digits across nine rounds for the tens place if it is not. It defaults to true.

How do I style number-flip?

The library builds a DOM structure of .number-flip, .ctnr and .digit elements and lets CSS produce the motion, with the container size derived from the digit size. You can rename the classes with containerClassName, digitClassName and separatorClassName in the constructor options.

Official sources

  1. gaoryrt/number-flip on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/gaoryrt-number-flip.svg)](https://hysenlabs.com/projects/gaoryrt-number-flip)

Was this useful?