Number Flip

Changed digits roll a full line, with blur and optional stagger.

Balance

$

Available across all accounts

"use client"

import { useState, ViewTransition } from "react"

export function NumberFlip({
  value,
  fractionDigits = 2,
  stagger = false,
  className = "",
}: {
  value: number
  fractionDigits?: number
  stagger?: boolean
  className?: string
}) {
  const [previous, setPrevious] = useState(value)
  const [direction, setDirection] = useState<"forward" | "back">("forward")
  if (previous !== value) {
    setPrevious(value)
    setDirection(value >= previous ? "forward" : "back")
  }
  const formatted = value.toLocaleString("en-US", {
    minimumFractionDigits: fractionDigits,
    maximumFractionDigits: fractionDigits,
  })
  return (
    <span aria-label={formatted} className={`inline-flex tabular-nums ${className}`}>
      {formatted.split("").map((char, index) => {
        const position = formatted.length - index
        const delay = stagger
          ? Math.min(formatted.slice(index + 1).replace(/\D/g, "").length, 2)
          : 0
        return /\d/.test(char) ? (
          <ViewTransition
            key={position}
            default={`vt-roll vt-${direction}${delay ? ` vt-delay-${delay}` : ""}`}
          >
            <span aria-hidden="true">{char}</span>
          </ViewTransition>
        ) : (
          <span key={position} aria-hidden="true">
            {char}
          </span>
        )
      })}
    </span>
  )
}

Installation

  1. 1Copy the Component tab into components/number-flip.tsx.
  2. 2Paste the CSS tab into globals.css, after Tailwind.

How it works

Each digit has a stable key based on its position from the right. Changed digits roll a full line in the direction of the value change. They start immediately; enable stagger for a short carry effect on slower updates.

Uses
one boundary per digit, vt-roll, vt-delay-*