Statistic

A number with a label, for dashboards and summaries, and a timer that counts down to a deadline or up from a start.

Accessibility

Statistic is markup, not a control: it renders no role and no ARIA of its own. Almost everything here is the page's job, and the first of those jobs is tying the number to the word beside it.

What it renders

Statistic is presentational markup, not a control. The root is a plain <div> with no role and no ARIA of its own. The title is a <div> inside a header div, and the value is a <span> inside a content div, with the prefix and the suffix in spans on either side of it.
  • Nothing ties the title to the value. There is no aria-labelledby, no <dl> and no heading. The pairing comes from their order and from sitting together on screen, so it stops reading correctly once something is placed between them.
  • role, aria-* and data-* reach the root div, so the page can name or group the tile from outside. Other unknown props are dropped.
  • A formatted number is split into two spans, one for the integer part and one for the decimals. They read as one string, but a selector that targets only one of them styles half a number.
  • A group of tiles is a group. Wrap the row in a landmark or a section with its own heading instead of leaving several unlabeled figures loose on the page.

How the number is read out

The formatting props change what is on the page, and that is what gets announced. Check these before you trust the reading.
  • groupSeparator puts a literal character inside the number. Screen readers differ on what they do with it, and a separator that is not the reader's own convention is the one most likely to come out as digits and punctuation.
  • A short unit is text. 4.8M and 12k are read as whatever the screen reader makes of the letter, and the listener cannot expand them. Where the exact figure matters, put an aria-label on the root that says the whole figure in words, and keep the short form on screen.
  • A symbol in prefix or suffix is announced in place, before or after the digits. % is fine. A bare arrow glyph needs a hidden word, or aria-hidden with the direction said elsewhere.
  • The value is always laid out left to right, which keeps digits in order on an RTL page. The title beside it is not, so check a real RTL page.

Color is never the direction

A green number and a red number are the same number to a screen reader, and to many sighted readers as well. Whatever a color says here has to be said a second way.
  • Pair the color with a sign and with an arrow in the prefix, so the direction survives grayscale, color blindness and forced-colors mode.
  • If the arrow is the only thing that shows direction, give it a word beside it, hidden with sr-only. If the sign already says it, mark the arrow aria-hidden.
  • A tinted tile follows the same rule: the tint is a hint, and the text on it still has to say which way things went.

A timer changing under a reader

Statistic.Timer updates about sixty times a second, whatever its format shows. Keep that in mind before making it a live region.
  • Do not make it a live region. An aria-live on a ticking timer queues announcements that never stop and drown out everything else on the page.
  • Announce the event, not the tick. Where the deadline matters, say it once in text (“offer ends at 18:00”) and use onFinish to announce the moment that changes something, in a polite live region of your own.
  • A countdown must never be the only way to learn a limit. Give the absolute time as well, so a reader who takes longer than the clock still knows what it said.
  • Timer removes the title attribute from the value, so a native tooltip cannot go there. Use a Tooltip around the timer instead.

Contrast

Two colors, two checks. The value is heading-colored at heading size, and the title is the muted description color at body size. The title is the one that fails.
  • The title's muted color is chosen against the page background. Put the tile on a tinted or colored card and check the title against the card, not the page.
  • The value is large text, so it clears a lower bar than the title. Shrinking it through classNames.value puts it back under the 4.5:1 rule, and nothing warns you.
  • A colored value is a third pair. Semantic reds and greens are chosen for the page background, so check them again on a tinted tile.
  • Check both modes. Theme classes switch with the mode, and a hex value written into a class does not.

Motion

Statistic has no transition of its own, but two things can move: loading shows a shimmering placeholder, and a Timer ticks for as long as it is mounted.
  • A shimmer is fine for a request and not as a resting state. Turn loading off when a request fails too, or a row of placeholders shimmers forever while nothing is being fetched.
  • A millisecond format keeps something moving in the corner of the eye. Show seconds unless the milliseconds are the point.
  • Anything you animate on top of the number (a count-up, a flash on change) belongs behind motion-safe:. Set motion: false in your theme's token to turn off the component transitions.