# Dither number

An animated number for KPI tiles and landing stats: tabular digits that roll or resolve through a dither, with Intl formatting, prefix and suffix, colour and size presets.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The dithercn agent skill, for building any dither on the engine, is at [/.well-known/agent-skills/dithercn/SKILL.md](/.well-known/agent-skills/dithercn/SKILL.md).

<ComponentPreview src="examples/dither-number-demo.tsx" size="lg" controls="numberControls">
  <DitherNumberDemo />
</ComponentPreview>

## Installation [#installation]

<InstallBlock name="dither-number" />

## Usage [#usage]

```tsx
import { DitherNumber } from "@/components/dithercn/dither-number";

export function Stat() {
  return <DitherNumber value={128406} size="xl" />;
}
```

When the number scrolls into view, every digit moves from `from` (0 by default) to its place in `value`. Change `value` later and only the digits that differ move, left to right, `stagger` milliseconds apart. While a digit moves it takes the `accent` colour, then fades back to the number's colour as it lands.

There are two effects:

* `roll` spins each digit on a strip, up when the value grows and down when it shrinks. The top and bottom of each digit's window dither out, so digits fade in and out of the line instead of being cut off. A place that moves by ten or more spins a full extra turn, like a mechanical counter.
* `resolve` turns the old digit into the new one cell by cell. The two use complementary dither masks, so each cell shows one digit or the other and they never blur together.

Digits are tabular, so the width only changes when the number of digits does. Separators, currency symbols, the `prefix` and the `suffix` keep their natural width and never move.

## Examples [#examples]

### KPI tiles [#kpi-tiles]

A dashboard row whose values update every few seconds. `size="sm"`, a muted `suffix` through `affixClassName`, and `resolve` and `roll` side by side. The latency tile uses an orange accent.

<ComponentPreview src="examples/dither-number-kpis.tsx" size="lg">
  <DitherNumberKpis />
</ComponentPreview>

### Landing stat [#landing-stat]

`size="display"` scales with the viewport. A longer `duration` and `stagger` make the first roll feel heavier; after that, the counter ticks up live and only the last few digits move.

<ComponentPreview src="examples/dither-number-landing.tsx" size="lg" align="stretch">
  <DitherNumberLanding />
</ComponentPreview>

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `number` |  | The number to show. Change it and the digits move to the new value. |
| `from` | `number` | `0` | Where the first run starts. |
| `format` | `Intl.NumberFormatOptions` |  | Currency, percent, compact notation, decimals and so on. |
| `locales` | `Intl.LocalesArgument` | `The visitor's locale` | Passed to Intl.NumberFormat. Set it when the server and client must format the same way. |
| `prefix` | `string` |  | Text before the number, such as "+". |
| `suffix` | `string` |  | Text after the number, such as " ms". |
| `affixClassName` | `string` |  | Classes for the prefix and suffix, for example a smaller, muted unit. |
| `effect` | `"roll" \| "resolve"` | `"roll"` | Digits spin on a strip, or turn into the new digit cell by cell. |
| `trigger` | `"mount" \| "in-view"` | `"in-view"` | Start the first run on mount, or when scrolled into view. |
| `size` | `"sm" \| "md" \| "lg" \| "xl" \| "display"` |  | A type preset: 2xl, 4xl, 6xl, 8xl, or a fluid display size. Leave it unset to size the number with your own classes. |
| `color` | `DitherColor` | `the text colour` | Colour of the number: a palette name or any CSS colour. |
| `accent` | `DitherColor \| "none"` | `"ink"` | Colour a digit takes while it moves, fading back as it lands. |
| `duration` | `number` | `900 (roll), 560 (resolve)` | Milliseconds per change, for one digit. |
| `stagger` | `number` | `40` | Milliseconds between one digit starting and the next. |
| `matrix` | `"bayer2" \| "bayer4" \| "bayer8"` | `"bayer4"` | The threshold pattern for the dithered edges and the resolve. |
| `cell` | `number` | `font size / 30` | Size of one dither cell in CSS pixels, at least 1. |

## Notes [#notes]

* The formatted value, with its prefix and suffix, is real text laid over the animation and made transparent, so it can be selected and copied and is what screen readers and search engines read. The moving digits are hidden from assistive technology. For a value that updates live, add `aria-live="polite"` yourself.
* The number sets `line-height: 1.1` so the dithered fade has room above and below the digits. A tighter `leading-*` class works, but the fade may touch the tops of tall glyphs.
* The server renders the final value. On the client the digits wait at `from` until the trigger fires.
* With reduced motion turned on, the number changes instantly.
* Only ASCII digits animate. Formats that use another numbering system show their digits without motion.