# Odometer Number

> Number that rolls each digit on its own spring reel, staggered left to right, when it scrolls into view or the value changes.

- Collection: Components
- Tier: free
- Docs: https://geturui.com/docs/number-ticker
- Install: `npx shadcn@latest add @geturui/number-ticker`
- Dependencies: motion

## Usage

```tsx
import { NumberTicker } from "@/components/ui/number-ticker";

export function Price({ yearly }: { yearly: boolean }) {
  return (
    <NumberTicker
      value={yearly ? 39 : 49}
      prefix="$"
      className="text-6xl font-semibold"
    />
  );
}
```

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| value | `number` | — | Number to show. Changing it rolls the reels. |
| prefix | `string` | "" | Text before the number, such as a currency sign. |
| suffix | `string` | "" | Text after the number, such as % or /mo. |
| decimals | `number` | 0 | Fixed number of decimal places. |
| from | `number` | 0 | Value shown before it scrolls into view. |
| stagger | `number` | 0.04 | Seconds between each digit starting to roll. |
| className | `string` | — | Classes for the wrapper; set the font size here. |

## Accessibility

- Screen readers get the final formatted value as one label, not the rolling digits.
- Uses tabular numerals so the width doesn't jitter while digits roll.
- Values are grouped with en-US separators (12,480) for readability.

## Source

### components/ui/number-ticker.tsx

```tsx
"use client";

import * as React from "react";
import { motion, useInView } from "motion/react";
import { cn } from "@/lib/utils";

export interface NumberTickerProps {
  value: number;
  prefix?: string;
  suffix?: string;
  decimals?: number;
  /** Start from this value on first view. */
  from?: number;
  /** Delay between digits, seconds. */
  stagger?: number;
  className?: string;
}

const DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function Reel({ digit, delay }: { digit: number; delay: number }) {
  return (
    <span className="relative inline-block h-[1em] overflow-hidden leading-none" aria-hidden>
      <motion.span
        className="flex flex-col"
        initial={false}
        animate={{ y: `${-digit}em` }}
        transition={{ type: "spring", stiffness: 160, damping: 20, delay }}
      >
        {DIGITS.map((d) => (
          <span key={d} className="block h-[1em] leading-none">
            {d}
          </span>
        ))}
      </motion.span>
    </span>
  );
}

/** Number that rolls each digit on its own spring reel, staggered left to right. Starts when scrolled into view. */
export function NumberTicker({ value, prefix = "", suffix = "", decimals = 0, from = 0, stagger = 0.04, className }: NumberTickerProps) {
  const ref = React.useRef<HTMLSpanElement>(null);
  const inView = useInView(ref, { once: true, margin: "-40px" });
  const shown = inView ? value : from;
  const fmt = (n: number) =>
    prefix + n.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) + suffix;
  const chars = fmt(shown).split("");

  return (
    <span ref={ref} className={cn("inline-flex items-baseline tabular-nums leading-none", className)} role="text" aria-label={fmt(value)}>
      {chars.map((c, i) => {
        const key = chars.length - i;
        return /\d/.test(c) ? (
          <Reel key={`d${key}`} digit={Number(c)} delay={i * stagger} />
        ) : (
          <span key={`s${key}`} aria-hidden>
            {c}
          </span>
        );
      })}
    </span>
  );
}

```
