Building a Number Flow Animation
A step-by-step guide to animating numeric values digit-by-digit with direction-aware motion, tabular alignment, and accessibility in mind.
In this tutorial, we'll build a Number Flow animation step by step. Use the + / − buttons in the preview to trigger the animation as each layer is added.
Static Number
Start by rendering a number with tabular-nums so digits stay aligned.
<span className="tabular-nums">{value}</span>Split into Digits
Convert the value to a string and animate each character independently.
const digits = value.toString().split("");<span className="tabular-nums"> {digits.map((d, i) => <span key={i}>{d}</span>)}</span>Animate Per Digit
Use AnimatePresence keyed by the character so each digit can enter and exit.
<AnimatePresence mode="popLayout" initial={false}> <motion.span key={digit + index} initial={{ y: 12, opacity: 0 }} animate={{ y: 0, opacity: 1 }} exit={{ y: -12, opacity: 0 }} > {digit} </motion.span></AnimatePresence>Direction-Aware
Slide up when the value increases, slide down when it decreases.
const direction = value > previous ? 1 : -1;initial={{ y: 12 * direction, opacity: 0 }}exit={{ y: -12 * direction, opacity: 0 }}Spring Timing
A quick spring with low bounce keeps the motion crisp.
transition={{ type: "spring", duration: 0.3, bounce: 0.1,}}Reduced Motion
Fall back to an instant swap when users prefer less motion.
const shouldReduceMotion = useReducedMotion();transition={shouldReduceMotion ? { duration: 0 } : { type: "spring", duration: 0.3, bounce: 0.1 }}Key Takeaways
After building this component, you've learned:
tabular-numskeeps digits at a fixed width — essential for any animated counter- Split by character so digits can enter and exit independently
AnimatePresencewithmode="popLayout"lets old and new digits coexist during transition- Direction-aware motion (sliding up vs. down) makes increments and decrements feel different
- Springs with low bounce read as "mechanical" — perfect for numbers and counters
- Reduced motion swaps instantly — the number still updates, just without the slide
Install the Component
npx smoothui-cli add number-flowCheck out the full documentation for all props and variations.