Meter
Displays a scalar measurement within a known range — disk usage, quota, a
score. Built on Base UI Meter. Unlike Progress, a meter is not about a
task advancing over time; it’s a static reading at a point in time.
Import
import { Meter } from "@photon-ai/pho-ui/components/meter";
Basic
Set value on Meter.Root (0–100 by default). Compose a header row with
Meter.Label and Meter.Value; with no format, the value reads as a
percentage of the range.
<Meter.Root value={72}>
<div className="flex items-center justify-between">
<Meter.Label>Storage used</Meter.Label>
<Meter.Value />
</div>
<Meter.Track>
<Meter.Indicator />
</Meter.Track>
</Meter.Root>
Range and formatting
Pass min / max to set the real bounds, and format (an
Intl.NumberFormatOptions) to render the value in its own units instead of a
percentage — bytes, GB, currency, a score out of five.
<Meter.Root
value={168}
max={256}
format={{ style: "unit", unit: "gigabyte", unitDisplay: "short" }}
>
<div className="flex items-center justify-between">
<Meter.Label>Disk</Meter.Label>
<Meter.Value />
</div>
<Meter.Track>
<Meter.Indicator />
</Meter.Track>
</Meter.Root>
Levels
The value maps to a level, so the fill can carry meaning. Override
Meter.Indicator’s fill with a semantic token past a threshold, and use
Meter.Value’s render prop for a raw “used / total” reading instead of a
percentage.
<Meter.Root value={1880} max={2000}>
<div className="flex items-center justify-between">
<Meter.Label>Build minutes</Meter.Label>
<Meter.Value>{(_, v) => `${v} / 2000`}</Meter.Value>
</div>
<Meter.Track>
<Meter.Indicator className="bg-pho-error-solid" />
</Meter.Track>
</Meter.Root>
Anatomy
- Meter.Root — owns
value,min,max; rendersrole="meter" - Meter.Label — names the measurement
- Meter.Value — renders the formatted value (percentage by default)
- Meter.Track — the rail
- Meter.Indicator — the fill; width is managed by Base UI
Props
Every part also accepts className and render (Base UI’s prop for swapping
the underlying element).
Meter.Root (<div role="meter">)
- value —
number(required) — the current reading - min —
number(default0) — start of the range - max —
number(default100) — end of the range - format —
Intl.NumberFormatOptions— howMeter.Valueformats the number; without it the value reads as a percentage of the range - locale —
Intl.LocalesArgument— locale for formatting; defaults to the runtime locale - getAriaValueText —
(formattedValue, value) => string— human-readable text foraria-valuetext - aria-valuetext —
string— static value text for assistive tech
Meter.Value (<span aria-hidden>)
- children —
(formattedValue, value) => ReactNode— render-prop override; defaults to the formatted percentage
Meter.Label (<span>), Meter.Track (<div>), Meter.Indicator (<div>) take only the common className / render; the indicator’s inline size is set by Base UI from value.
Accessibility
- Semantics —
Meter.Rootrendersrole="meter"witharia-valuemin,aria-valuemax,aria-valuenow, andaria-valuetext, exposing the reading to assistive tech.Meter.Valueisaria-hidden(its text duplicatesaria-valuetext), so the number isn’t announced twice. - Labelling — a
Meter.Labelis linked viaaria-labelledby. With no visible label, passaria-labelonMeter.Rootso the meter isn’t anonymous;getAriaValueTextsupplies a friendlier reading (“168 of 256 GB”). - Not interactive — it’s a static reading, not a control: nothing to focus and no keyboard interaction.
- Announcements — the
meterrole reports the current value but isn’t a live region, so changes aren’t pushed to assistive tech. A meter is for a stable measurement, not a running commentary — useProgressfor that.
Best practices
- Choose
MeteroverProgresswhen the number reflects a level, not a task in flight. “Storage used” is a meter; “Uploading…” is progress. - Pair the bar with a label and value so the reading is legible without estimating the fill by eye.
- Keep the range honest — set
min/maxto the real bounds so the fill maps to the actual proportion. - Reach for a semantic fill only to signal a real threshold (near quota, low balance); color for its own sake dilutes the signal.