Meter

Shows a measured value within a known range — storage used, password strength, battery — as a labelled bar. Not a progress indicator.

Basic usage

Install the package:

pnpm add @design-schema/react @design-schema/tokens

Import the stylesheet once for the app, then the component:

import '@design-schema/tokens/calm-precise/css';
import '@design-schema/react/index.css';
import { Meter } from '@design-schema/react';

Examples

Storage used32%
import { Meter } from '@design-schema/react';

<Meter label="Storage used" value={32} />

Props

7 rows
PropTypeDefaultDescriptionPlatforms
value requirednumber—

The current measurement. Clamped to `min`…`max` for the bar; the accessible value is the clamped number too, exact and unrounded (aria-valuenow="3.14159"; only the percentage text is rounded). Lit starts the property at 0, with no development warning.

web, lit, rn, swiftui
minnumber0

Lower bound of the range. On Lit a missing or unparseable attribute falls back to 0; on every platform a non-finite `min` (NaN, Infinity) is treated as 0, and it is the substituted bound that is exposed — aria-valuemin is always a finite number, never "NaN". The reflected `min` attribute still carries whatever was set, so the property and the announced range can differ; the clamp lives in the getters, not in a normaliser that rewrites the attribute.

web, lit, rn, swiftui
maxnumber100

Upper bound of the range. Must be greater than `min`. On Lit a missing or unparseable attribute falls back to 100; on every platform a non-finite `max` (NaN, Infinity) is treated as 100, and the substituted bound is the one exposed, as for `min`. An invalid range warns once per distinct min/max pair for the life of the process — a module-level record, so a second Meter with the same bad range is silent and a remount never repeats it. The warning text is a development diagnostic; the backticks it is written with here are this document's formatting, not characters in the emitted string.

web, lit, rn, swiftui
label requiredstring—

Visible label naming the measurement ("Storage used"). Also the accessible name. Lit starts the property as an empty string, with no development warning.

Accessibility: Associated with the meter as its accessible name (aria-labelledby / accessibilityLabel).

web, lit, rn, swiftui
valueTextstring—

Human-readable value shown at the end of the label row and announced instead of the raw number ("3.2 GB of 10 GB", "Strong"). Omit to show and announce the percentage, rounded to a whole number ("32%"): `Intl.NumberFormat(undefined, { style: 'percent', maximumFractionDigits: 0 })` of the fill fraction. Meter has no locale prop: every platform uses the runtime (browser, server or device) default locale, and the same formatter produces the "0%" of an invalid range (so "0 %" in fr). Rounding is for the text only; the fill width uses the exact fraction. For the same locale, the announced string is the same on every platform. Lit attribute `value-text`, not reflected.

Accessibility: Always rendered, as aria-valuetext (web, Lit) and accessibilityValue.text (React Native): valueText when given, else the formatted percentage.

web, lit, rn, swiftui
toneinfo | success | warning | dangerinfo

Fill color. `info` is the neutral brand fill; the consumer sets `success`/`warning`/`danger` from thresholds it owns — the meter does not decide what is "too full".

web, lit, rn, swiftui
hideValuebooleanfalse

Hides the visible value text (a boolean attribute can only turn things on, so the flag is the hiding one). The header row stays — the label is always visible — and only the value text and its wrapper are omitted. The accessible value is always exposed. Lit attribute `hide-value`, not reflected.

web, lit, rn, swiftui

Accessibility

Decisions this component leaves to you

Supply an accessible name. This component has no text of its own to fall back on, so without one a screen reader announces its role and nothing else.

Required props that carry accessibility meaning: label. Each is described in the props table above.

Implicit ARIA role: meter.

Anatomy

containerheaderlabelvalueTexttrackfill

What generation guarantees

Accessible name — Every instance carries a programmatic name. Where the component has no text of its own to use, the name is a prop the consumer supplies.

Contrast — Text and meaningful non-text contrast meets WCAG AA; the pairs are listed below.

Reduced motion — Under `prefers-reduced-motion: reduce` the transitions are dropped rather than shortened, and the component still reaches every state.

Contrast pairs

4 rows
ForegroundBackgroundLevel
color.status.{tone}.iconcolor.background.strongAA (non-text)
color.status.{tone}.iconcolor.backgroundAA (non-text)
color.foregroundcolor.backgroundAA
color.foreground.mutedcolor.backgroundAA

Under `prefers-reduced-motion: reduce` the transitions are dropped rather than shortened, and the component still reaches every state.