NumberFlow

 for 
431.10

An animated number component. Dependency-free. Accessible. Customizable.

Basic usage

<!-- Basic usage -->
<script setup>
import NumberFlow from '@number-flow/vue'
</script>
<template>
	<NumberFlow :value="123" />
</template>

<NumberFlow> will automatically transition when the value prop changes.

Props

format: Intl.NumberFormatOptions

Formatting options for the number; see MDN for a full reference.

<NumberFlow :format="{ notation: 'compact' }" :value />

locales: Intl.LocalesArgument

The locale(s) for the number; see MDN for more information.

prefix: string, suffix: string

A custom prefix or suffix for the number.

$3/moClick anywhere to change numbers
<NumberFlow
	:value
	:format="{ style: 'currency', currency: 'USD', trailingZeroDisplay: 'stripIfInteger' }"
	suffix="/mo"
/>

Timings

There are three props to customize the animation timings. Each accept an EffectTiming object:

124.23Click anywhere to change numbers
<NumberFlow
	:transformTiming="{
		// Used for layout-related transforms:
		duration: 750, easing: 'linear(...)'
	}"
	:spinTiming="{
		// Used for the digit spin animations.
		// Will fall back to `transformTiming` if unset:
		duration: 750, easing: 'linear(...)'
	}"
	:opacityTiming="{
		// Used for fading in/out characters:
		duration: 350, easing: 'ease-out'
	}"
/>

For spring-based easings, I’d recommend Kevin Grajeda’s generator or easing.dev.

trend: number (oldValue: number, value: number) => number Default: (oldValue, value) => Math.sign(value - oldValue)

Controls the direction of the digits. If trend is or returns

  • +1: the digits always go up.
  • 0: each digit goes up if it increases and down if it decreases. This can be useful if you want to animate number changes without conveying an overall trend (example).
  • -1: The digits always go down.
20Click anywhere to change numbers

animated: booleanDefault: true

Can be set to false to disable all animations and finish any current ones. See the input example for a usage scenario.

digits: Record<number, { max?: number }>

Configure digits based on their position in the number (i.e. for 342.5, the positions are: 324120.5-1). This can be helpful for time-related displays, to ensure e.g. 59 -> 00. See the countdown example for a demo.

digits is not reactive to save on bundle size. If you need it to be reactive, please submit a feature request.

willChange: booleanDefault: false

If set, NumberFlow applies will-change properties to relevant elements. This can be useful if:

  • Your number is guaranteed to change frequently
  • You experience unwanted repositioning when a transition completes

Note that “excessive use of will-change will result in excessive memory use” (source: MDN).

respectMotionPreference: booleanDefault: true

Can be set to false to animate regardless of the user’s reduced motion preference.

plugins: Plugin[]

Plugins to apply to the component. Currently there’s only one plugin, continuous, which makes the number transitions appear to pass through in-between numbers:

120
Click anywhere to change numbers

This plugin has no effect if trend is 0.


Emits

animationsstart: (e: CustomEvent) => void

Triggered when update animations start.

Not to be confused with the built-in animationstart event, which would trigger for animations on the <NumberFlow> element itself.

animationsfinish: (e: CustomEvent) => void

Triggered when update animations finish.


Styling

NumberFlow uses a custom element under the hood, and exposes parts for styling purposes:

$3/moClick anywhere to change numbers

You can use your browser’s inspector to see which part attributes are available to style. Note that changing the font-size of digits is difficult due to the CSS techniques NumberFlow uses.

::part styles may cause a flash of unstyled content in old browsers.

See workaround You can use feature detection to apply ::part styles only to browsers that support Declarative Shadow DOM (DSD). Add the following snippet to your <head>:

<script>
	if (
		HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode') ||
		HTMLTemplateElement.prototype.hasOwnProperty('shadowRoot') // old Chrome/Edge
	)
		document.documentElement.setAttribute('data-supports-dsd', '')
</script>

Then ensure your ::part styles use it:

:root[data-supports-dsd] number-flow-vue::part(suffix) {
	font-size: 0.75rem;
}

If you’re using Tailwind, you can do this with a custom variant:

// tailwind.config.js
import plugin from 'tailwindcss/plugin'

export default {
	// ...
	plugins: [
		plugin(({ matchVariant }) => {
			matchVariant('part', (p) => `:root[data-supports-dsd] &::part(${p})`)
		})
	]
}
<NumberFlow class="part-[suffix]:text-xs" />

There’s also some CSS properties you can use to style the component:

--number-flow-mask-[height|width]: <length>Default: .25em | .5em

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

--number-flow-char-height: <length>Default: 1em

Sets the height of each character. This can be used to adjust the spacing between digits during spin animations.

font-variant-numeric: tabular-nums

Ensures all numbers are the same width, which can prevent digits from shifting during transitions. See MDN for more information.


Grouping

If a <NumberFlow> affects another <NumberFlow>’s position, you can wrap them in a <NumberFlowGroup> to properly sync their transitions:

$124.23+5.64%
Click anywhere to change numbers

<NumberFlowGroup> doesn’t render an element or accept any props.


Composables

useCanAnimate(opts?: { respectMotionPreference?: MaybeRefOrGetter<boolean> }): ComputedRef<boolean>

Returns a computed ref whose value is true if NumberFlow can animate, i.e. the browser supports the required features and (optionally) the user is okay with motion.


Limitations