Rolling number
NewA figure that reads like a till display. Every digit is a reel, so when the number changes each column rolls to its new digit. Reach for it for the figures that lead a view and change while someone is watching: today's takings, a live count.
Basics
Pass a number and a format. When the value changes, each digit rolls to its new place like an odometer (9 to 0 steps forward, never back through every digit) and each column to the right starts 25ms after its neighbour, so the roll reads as mechanical. The £ sign and the separators stay still. Press Simulate a sale.
Formats
format takes the same options as Intl.NumberFormat, and locale defaults to en-GB, so money, counts and rates come out the UK way. Percentages take a fraction: pass 0.031 to show 3.1%.
{ style: 'currency', currency: 'GBP' }No format: grouped the UK way{ style: 'percent', maximumFractionDigits: 1 }{ ...gbp, notation: 'compact' }{ ...gbp, maximumFractionDigits: 0 }Gaining and losing digits
Columns are keyed by place value (ones, tens, hundreds), so a column keeps its reel as the number grows or shrinks. Going from 9,999 to 10,000 rolls the existing columns and opens a new one on its grid track, with its group separator; dividing closes columns the same way.
First appearance
By default a figure counts up from zero the first time it renders, which gives a page's leading number an arrival. Turn it off with animateOnMount={false} where the figure should simply be there: in tables, in lists, and when a page is restored.
Type and tabular figures
The reel inherits its font, size and colour, so the same component sits in a table cell in Geist or as the headline figure of a dashboard in Sora. Every numeral is tabular and each column is held open at the width of a zero, so figures never jitter as they roll and amounts line up in a column.
In both themes
Ink on the canvas in light, night ink in dark. The figure never takes a colour of its own.
Motion
The roll runs on the settle spring, which lands with a whisper of overshoot, like a reel clicking into place.
| Moment | What happens |
|---|---|
| A digit changes | Its reel rolls to the new digit in 0.9s (set with duration), 25ms after the column to its left. |
| The number gains a digit | A new column opens on its grid track and fades in over 480ms. |
| The number loses a digit | The column rolls to zero and closes over about 360ms. |
| First render | Every reel counts up from zero, unless animateOnMount is false. |
Reduced motion
With reduced motion on, nothing rolls. A changed digit fades in where the old one was, a first render shows the figure at once, new columns fade in and old ones go at once. Every change is still visible; only the movement is dropped.
Guidelines
You have taken £2,950.30 today from 23 orders, which is up on yesterday.
- Pass numbers, not formatted strings. Formatting happens inside, which is how each digit keeps its column.
- In tables and long lists, turn off the arrival with
animateOnMount={false}. A column of reels spinning on every sort reads as the data changing. - Show how a figure moved with Delta beside it, not by colouring the figure. Delta also writes a fall with a true minus sign.
- Keep the unit in the format (a £ sign, a % sign) rather than in text next to the figure, so it stays put while the digits roll.
Accessibility
- The reels are hidden from assistive tech. Screen readers read the plain formatted value from a visually hidden copy beside them, and copying the figure copies that value.
- Pass
liveto announce changes as they happen (aria-live="polite", read as a whole). It is off by default: a figure that refreshes every few seconds would otherwise talk over everything else. - Rolling respects reduced motion: digits swap with a fade instead.
Props
Also accepts every native span attribute except children.
| Prop | Type | Default | Description |
|---|---|---|---|
valueRequired | number | None | The number to show. Non-finite values show as 0. |
format | Intl.NumberFormatOptions | None | Formatting, e.g. { style: 'currency', currency: 'GBP' }. |
locale | string | 'en-GB' | Locale for formatting. |
duration | number | 0.9 | Seconds one roll takes. |
animateOnMount | boolean | true | Count up from zero on first render. |
live | boolean | false | Announce changes to screen readers as they happen (aria-live="polite"). |