MotionNumber v0.1.7

312

Transition, format, and localize numbers with a ~3kB Framer Motion component. Accessible and customizable.

GitHub
// Basic usage
import MotionNumber from 'motion-number'

<MotionNumber
    value={value}
    format={{ notation: 'compact' }} // Intl.NumberFormat() options
    locales="en-US" // Intl.NumberFormat() locales
/>

See MDN’s Intl.NumberFormat() reference for a full list of locales and format options.

Customizing

MotionNumber is a Motion component, so it accepts a transition prop to customize the transitions:

12,398.4Click anywhere to change numbers
import { easeOut } from 'framer-motion'

<MotionNumber
    transition={{
        // Applied to layout animations on individual characters:
        layout: { type: 'spring', duration: 0.7, bounce: 0 },
        // Used for the digit animations:
        y: { type: 'spring', duration: 0.7, bounce: 0.25 },

        // Opacity applies to entering/exiting characters.
        // Note the use of the times array, explained below:
        opacity: { duration: 0.7, ease: easeOut, times: [0, 0.3] } // 0.3s perceptual duration
    }}
/>

One important note: if you override opacity, make sure to give it the same duration as the layout animation. This improves animation performance by ensuring Framer Motion doesn’t remove exiting children until the layout animation ends. If you want the opacity transitions to look shorter than the layout animations, you can use the times array with the format [0, perceptualDuration], as shown above. Also, make sure to use an easing function rather than strings like 'linear' or 'easeIn', as they seem to work better. If you’re using <MotionConfig> to set a transition, make sure to check the opacity settings there as well.


Beyond the transitions, there’s some CSS properties you can use to customize the display:

--mask-height/--mask-width

These adjust the height and width of the gradient fade-out masks at the edges of the number. --mask-height also gets used as the vertical padding for the number.

line-height

In my opinion, the transitions look better with small vertical gaps between numbers, because it reduces the distance they travel. I recommend using the smallest line-height that fits all your component’s characters:

<MotionNumber style={{ lineHeight: 0.8 }} /* ... */>

The exact value will depend on your font and formatting options. MotionNumber uses a safe default line-height of 1, set through an inline style for simplicity. This means to override it globally you’ll have to use !important (sorry about that; React 19 should clean this up):

[data-motion-number] {
    line-height: 0.8 !important;
}

Grouping

MotionNumber has four render props that can be used to synchronize other content with the number transitions:

These can be combined to create interesting effects:

$124.235.64%Click anywhere to change numbers

These are also helpful because LayoutGroup doesn’t currently work with MotionNumber; see the note in limitations.

LazyMotion

If you’re using <LazyMotion>, you can import the lazy version of MotionNumber with:

import MotionNumber from 'motion-number/lazy'

The API is the same.

Limitations

Credits

Built by Max Barvian. Heavily inspired by the Family iOS app. Technique for the mask-image from Artur Bień.