Skip to documentation content

Meter

Show a known measurement within a fixed range, such as storage or a score.

meter-demo

Overview

Use Meter to display a known numeric value within a known range: storage used against quota, budget spent against budget, signal strength, password strength, capacity, a score. The value is a measurement of the current state, not a task advancing toward an end. For work in motion — uploads, imports, installs — use Progress instead.

Anatomy

A meter has a root carrying the value semantics, an optional label naming what is measured, an optional formatted value, and a visual bar or ring. With no children, Meter renders the matching track for its variant plus a centered value automatically.

Progress vs Meter

The two components look alike and differ in meaning, and that difference is exposed to assistive technology through their roles: Meter emits role="meter" while Progress emits role="progressbar". A screen-reader user hearing "progressbar" expects a value that grows and a moment when it finishes; "meter" promises a reading that can go either way and never completes.

Ask one question: does this number end? An import at 72% will hit 100% — Progress. Storage sitting at 72% of quota is just storage — Meter. Quota consumption does creep upward over weeks, but it is not a task, there is no completion event, and nobody benefits from a component that implies one.

Usage guidance

Always give the meter a visible label and, when precision matters, render MeterValue or format the announced text yourself — "14.2 GB of 20 GB used" carries more meaning than a bar and "72". When different portions of the range have different meanings (low, normal, high), change the indicator color per band but pair it with wording such as "Weak signal" or "Strong signal"; color alone excludes too many people.

Accessibility

The root emits role="meter" with aria-valuemin, aria-valuemax, and aria-valuenow. Screen readers announce the accessible name plus the current value against the range. Connect MeterLabel for the name, and use format, locale, or getAriaValueText(formattedValue, value) when the default percentage is less informative than real units. Render the value as text for sighted users too — bar length is a poor instrument for comparing 68% against 74%.

Meters have no indeterminate or loading state by design: a measurement is either known or it isn't. While the underlying value is being fetched, show a Skeleton rather than a zeroed meter, which would falsely read as empty. Value changes animate with a short transition that is skipped under reduced-motion preferences. Colors come from theme tokens and adapt to dark mode automatically.

Installation

npx honestui@latest add meter

Usage

import {
  Meter,
  MeterCircularTrack,
  MeterIndicator,
  MeterLabel,
  MeterTrack,
  MeterValue,
} from "@/components/ui/meter";
<Meter value={40}>
  <div className="flex items-center justify-between">
    <MeterLabel>Progress</MeterLabel>
    <MeterValue />
  </div>
  <MeterTrack>
    <MeterIndicator />
  </MeterTrack>
</Meter>
<Meter value={40} variant="circular">
  <MeterCircularTrack />
  <MeterValue />
</Meter>

Non-zero minimums work: <Meter min={20} max={80} value={65} /> maps 65 into the correct fraction of the bar.

Don't do this

A task pretending to be a measurement

// Bad
<Meter value={uploadedBytes / totalBytes * 100}>
  <MeterLabel>Uploading photos</MeterLabel>
</Meter>
// Good
<Progress value={uploadedBytes / totalBytes * 100}>
  <ProgressLabel>Uploading photos</ProgressLabel>
</Progress>

role="meter" tells assistive technology this is a static reading, so users will not wait for completion feedback that never arrives — and the UI has no natural way to say "done". Anything with an endpoint belongs to Progress.

Thresholds conveyed by color only

// Bad
<Meter value={95} className="[&_[data-slot=meter-indicator]]:bg-red-500">
  <MeterLabel>CPU</MeterLabel>
</Meter>
// Good
<div>
  <p className="text-sm font-medium">CPU — critical load</p>
  <Meter value={95}>...</Meter>
</div>

Red at 95% is invisible to colorblind users, washed out in forced-colors mode, and silent to screen readers. Keep the color if you like it, but state the verdict in words where everyone can get it.

A bare number without units

// Bad
<Meter value={72} />
// Good
<Meter value={72} format={{ style: "unit", unit: "percent" }}>
  <MeterLabel>Storage used</MeterLabel>
  <MeterValue />
</Meter>

An unlabeled bar with no value text answers no question: 72 of what, out of how much, measured when? Name the measurement and show its units; use format or getAriaValueText so the announcement matches what sighted users see.

Examples

Storage quota

Real units on both sides of the comparison.

meter-storage-quota

Without Label and Value

The automatic layout with only the root's children generated.

meter-simple

With Formatted Value

meter-with-formatted-value

With Range

A non-zero minimum and maximum mapping values into a partial band.

meter-with-range

Circular Sizes

Set --hui-meter-size and --hui-meter-track-size on MeterCircularTrack to change the rendered diameter and stroke without editing its SVG geometry. Keep the centered value readable as the ring gets smaller.

meter-circular-sizes

Circular Semantic States

You can change the circular indicator stroke with a semantic color token. Pair that color with a visible label such as “Weak signal” or “Strong signal” so the state does not depend on color alone.

meter-circular-statuses

API reference

Meter accepts all Base UI Meter root props plus Honest UI's variant:

PropValuesDefault
variantlinear, circularlinear
valuenumber (required)
min / maxnumber0 / 100

Unlike Progress, value must be a number — there is no indeterminate state. The root carries role="meter" with aria-valuemin, aria-valuemax, aria-valuenow, and aria-valuetext; format (Intl options), locale, and getAriaValueText(formattedValue, value) control the announced text. Computed percentages are clamped to 0–100.

Parts: MeterLabel, MeterTrack, MeterIndicator, and MeterValue forward their Base UI props; MeterCircularTrack accepts native SVG props plus the --hui-meter-size and --hui-meter-track-size CSS variables.

See the Base UI Meter API for value formatting and state details.