Number Flip

Changed digits roll a full line, with an optional stagger.

Balance

$6,578.42

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 className={`inline-flex tabular-nums ${className}`}>
      <span className="sr-only">{formatted}</span>
      <span aria-hidden="true" className="inline-flex">
        {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>{char}</span>
            </ViewTransition>
          ) : (
            <span key={position}>{char}</span>
          )
        })}
      </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-*