Meter
Gauge displaying a known value within a fixed range — battery, disk usage, score, capacity. Linear or circular, four sizes, full intent palette with optional threshold-based auto-coloring, locale-aware formatting, and a multi-segment MeterGroup variant for stacked usage.
Playground
Installation
pnpm add @tessinaui/uiUsage
import { Meter, MeterGroup } from "@tessinaui/ui";{/* Basic */}
<Meter value={68} label="Storage" />
{/* Circular */}
<Meter type="circular" value={72} label="CPU" valuePosition="inside" />
{/* Threshold-based auto colour */}
<Meter
value={92}
label="Disk usage"
thresholds={[
{ at: 50, intent: "primary" },
{ at: 80, intent: "warning" },
{ at: 101, intent: "error" },
]}
/>
{/* Locale-aware formatting */}
<Meter
value={186.4}
max={500}
label="Disk"
format={{ style: "unit", unit: "gigabyte", maximumFractionDigits: 1 }}
/>
{/* Custom range — rating */}
<Meter value={4.3} max={5} label="Rating" intent="success" showFraction />
{/* Multi-segment */}
<MeterGroup
max={500}
showTotal
format={{ style: "unit", unit: "gigabyte", maximumFractionDigits: 1 }}
segments={[
{ label: "Photos", value: 124, intent: "primary" },
{ label: "Videos", value: 86.5, intent: "error" },
{ label: "Music", value: 42.3, intent: "info" },
{ label: "Other", value: 31.2, intent: "neutral" },
]}
/>Showcase
Meter vs Progress
The two components look similar but communicate different things:
| Meter | Progress | |
|---|---|---|
| Purpose | A known value within a fixed range | Task completion working toward 100% |
| Examples | Battery 78%, Disk 320 GB / 512 GB, Score 4.2 / 5, RAM 6.2 GB / 16 GB | File upload, multi-step form, page load |
| Range | Custom min / max (default 0–100) | Always 0–100 |
| Indeterminate | No — value must be known | Yes — indeterminate prop |
| ARIA role | role="meter" | role="progressbar" |
Use Meter when the value already exists and isn't going to change as the user waits. Use Progress when something is loading.
API Reference — Meter
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | The current value (clamped between min and max) |
min | number | 0 | Minimum |
max | number | 100 | Maximum |
type | "linear" | "circular" | "linear" | Bar or ring |
size | "sm" | "md" | "lg" | "xl" | "md" | Size token |
rounded | "none" | "sm" | "md" | "lg" | "full" | "full" | Corner radius (linear only) |
intent | "primary" | "error" | "warning" | "success" | "info" | "neutral" | "primary" | Indicator colour |
tone | "solid" | "soft" | "solid" | Saturation — soft uses *-light token |
thresholds | Array<{ at: number; intent: MeterIntent }> | — | Auto-recolor based on value crossing breakpoints |
label | ReactNode | — | Visible label rendered above (linear) or below (circular) the meter |
labelPosition | "outside" | "inside" | "none" | "outside" | Where the label appears |
valuePosition | "right" | "below" | "inside" | "none" | "right" | Where the formatted value renders |
showFraction | boolean | false | Render as ${value}/${max} instead of ${value} |
format | Intl.NumberFormatOptions | — | Locale-aware formatting (currency, percent, units, compact) |
locale | Intl.LocalesArgument | runtime | BCP-47 locale tag |
renderValue | (formatted: string, value: number) => ReactNode | — | Fully custom value rendering — overrides format / showFraction |
dir | "ltr" | "rtl" | inherited | Text direction |
className | string | — | Additional classes on the root |
Thresholds
thresholds lets the meter recolour itself based on the current value. The lowest at whose value is greater than the current value wins. If no threshold matches, the base intent is used.
<Meter
value={92}
thresholds={[
{ at: 30, intent: "error" }, // 0–29 → error
{ at: 70, intent: "warning" }, // 30–69 → warning
{ at: 101, intent: "success" }, // 70–100 → success
]}
/>For an inverted scale (e.g. battery — low is bad), reverse the order:
<Meter
value={15}
thresholds={[
{ at: 20, intent: "error" },
{ at: 50, intent: "warning" },
{ at: 101, intent: "success" },
]}
/>API Reference — MeterGroup
| Prop | Type | Default | Description |
|---|---|---|---|
segments | Array<MeterGroupSegment> | — | Stacked segments that fill the bar |
min | number | 0 | Minimum value of the scale |
max | number | "sum" | 100 | Max value, or "sum" to derive from segment values |
size | "sm" | "md" | "lg" | "xl" | "md" | Size token |
rounded | "none" | "sm" | "md" | "lg" | "full" | "full" | Track corner radius |
label | ReactNode | — | Label rendered above the bar |
legendPosition | "above" | "below" | "none" | "below" | Where to render the segment legend |
showTotal | boolean | false | Render ${total} / ${max} next to the label |
format | Intl.NumberFormatOptions | — | Format the total + each legend value |
locale | Intl.LocalesArgument | runtime | BCP-47 locale tag |
dir | "ltr" | "rtl" | inherited | Text direction |
MeterGroupSegment
| Field | Type | Description |
|---|---|---|
value | number | Segment value (in same scale as min/max) |
label | ReactNode | Display label in the legend |
intent | MeterIntent | Colour for this segment (default "primary") |
tone | "solid" | "soft" | Saturation (default "solid") |
icon | ReactNode | Optional icon next to the legend label |
Notes
- Accessibility — built on
@base-ui/react/meter. The container renders withrole="meter"andaria-valuemin/aria-valuemax/aria-valuenow/aria-valuetextso assistive technologies announce the value correctly. - Locale-aware aria-valuetext — when
formatis provided, the value is formatted withIntl.NumberFormatforaria-valuetextso screen readers read e.g. "42 percent" rather than "0.42". - Inside value — only
lgandxllinear sizes render an inline value (the smaller sizes don't have enough vertical room). - RTL — the linear track is mirrored via
transform: scaleX(-1). The inside-value text is un-mirrored so it reads correctly. max="sum"onMeterGroup— convenient when you want the bar to be exactly as long as the data fills it (e.g. a triage panel showing exact bug counts), rather than a fixed scale.
Progress
Communicate operation status with a linear bar or circular ring. Four variants (default/success/warning/error), three sizes, five rounding options, indeterminate animation, optional label with inside/outside positioning, and LTR/RTL support.
Rating
Captures user sentiment in four patterns — star scale, numeric NPS scale, emoji reaction picker, and binary thumbs — with full intent palette, half-star precision, RTL support, and keyboard navigation.