---
title: "Meter"
description: "Shows where a known value stands in a known range — storage used, a battery's charge, a quota, the strength of a password — as a bar or a ring that looks like the progress indicator, without saying that anything is under way."
url: "https://tanrei.chnotchy.com/components/meter/"
last_updated: 2026-10-23T21:17:12.000Z
---

# Meter

> Shows where a known value stands in a known range — storage used, a battery's charge, a quota, the strength of a password — as a bar or a ring that looks like the progress indicator, without saying that anything is under way.

## Overview

### Usage

The Web and React samples show the current spec. SwiftUI, Compose, Flutter and React Native are planned: they do not ship a meter yet.

**Web**

```html
<tanrei-meter value="0.62" label="Storage used" value-text="9.3 GB of 15 GB"></tanrei-meter>
<tanrei-meter value="37" max="50" label="Seats taken" value-text="37 of 50 seats"></tanrei-meter>

<!-- The product decides what the value means, and says it in words too. -->
<tanrei-meter value="0.85" status="warning" label="Storage used" value-text="Almost full: 12.8 GB of 15 GB"></tanrei-meter>

<tanrei-meter variant="circular" size="small" value="0.62" label="Battery"></tanrei-meter>

<script type="module">
  import '@tanrei/web/components/meter';
  const meter = document.querySelector('tanrei-meter');
  meter.value = used / quota;
  meter.status = used / quota > 0.8 ? 'warning' : 'default';
</script>
```

**React**

```tsx
import { Meter } from '@tanrei/react';

<Meter value={used / quota} label="Storage used" valueText={`${usedText} of ${quotaText}`} />
<Meter value={seats} max={50} label="Seats taken" valueText={`${seats} of 50 seats`} />
<Meter
  value={used / quota}
  status={used / quota > 0.8 ? 'warning' : 'default'}
  label="Storage used"
  valueText={used / quota > 0.8 ? `Almost full: ${usedText} of ${quotaText}` : `${usedText} of ${quotaText}`}
/>
<Meter variant="circular" size="small" value={charge} label="Battery" />
```

## Specs

- Category: Communication
- Level: base
- Listing: default
- Page: https://tanrei.chnotchy.com/components/meter/
- Spec: https://github.com/chnotchy/tanrei/blob/main/specs/meter.spec.json

### Availability

| Design system | Has it | Notes |
| --- | --- | --- |
| Tanrei (`tanrei`) | Yes | — |
| Cupertino (Apple HIG-inspired) (`cupertino`) | Yes | — |
| M3 Expressive-like (Material 3 Expressive-inspired) (`m3-expressive-like`) | Yes | — |
| M3-like (Material 3-inspired) (`m3-like`) | Yes | — |

### Platforms

- Web: the element `<tanrei-meter>`, registered by importing `@tanrei/web` (or only its own module, `@tanrei/web/components/meter`).
- React: `Meter` from `@tanrei/react`.

| Platform | Name | Status |
| --- | --- | --- |
| Web | `tanrei-meter` | alpha |
| SwiftUI | `TanreiMeter` | planned |
| Jetpack Compose | `TanreiMeter` | planned |
| Flutter | `TanreiMeter` | planned |
| React Native | `Meter` | planned |

Names a platform gives in its own idiom:

| Spec | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
| --- | --- | --- | --- | --- | --- |
| `min` (prop) | `min` | `bounds.lowerBound` | `valueRange.start` | `min` | `min` |
| `max` (prop) | `max` | `bounds.upperBound` | `valueRange.endInclusive` | `max` | `max` |

Statuses: `planned` — In the inventory. Nothing shippable exists yet. `lagging` — An implementation of an earlier spec exists; it has not caught up with the current one. `alpha` — Implemented with tests. The API may still change. `beta` — Feature complete and at parity. Only fixes are expected. `stable` — Covered by semantic versioning. `deprecated` — Still shipped; scheduled for removal.

### Anatomy

- `track`: The full length of the bar, or the full circle of the ring: the whole range from min to max.
- `indicator`: The part of the track drawn in the indicator color of the meter's status: the fraction of the range up to the value. It meets the rest of the track, or stands a gap from it (separation).
- `stop-indicator` (optional): A dot at the end of a bar's track that marks where the range ends, drawn whenever its size is above 0.

### Props

| Prop | Type | Default | Web attribute | Description |
| --- | --- | --- | --- | --- |
| `variant` | `enum`: `linear`, `circular` | `"linear"` | `variant` | Linear is a bar that fills the width its layout gives it; circular is a ring of a fixed size, for tight places and beside text. A structural choice drawn by code, not a token axis, as the progress indicator's is. |
| `size` | `enum`: `small`, `medium`, `large` | `"medium"` | `size` | The bar's thickness or the ring's box, the progress indicator's three sizes by default (2 / 4 / 8 and 16 / 24 / 40). A design system may add values. |
| `separation` | `enum`: `auto`, `divider`, `gap` | `"auto"` | `separation` | How the indicator is told from the rest of its track. divider: drawn on the track with no space between them. gap: empty space between them. Auto takes comp.meter.track.separation, which follows the design system's choice. |
| `value` | `number` | `0` | `value` | Where the measured quantity stands, between min and max. Always a number: a meter has no unknown value, and a task under way is a progress indicator. |
| `min` | `number` | `0` | `min` | The low end of the range. |
| `max` | `number` | `1` | `max` | The high end of the range. |
| `status` | `enum`: `default`, `info`, `success`, `warning`, `error` | `"default"` | `status` | What the product says the value means, in the names the tag and the snackbar use: default draws the indicator in the meter's own color; info, success, warning and error in that status's color. The product decides it — the meter has no thresholds of its own — and it changes colors only: the words that say so are valueText's and the product's. |
| `label` | `string` | `""` | `label` | The accessible name: what is measured ("Storage used"). Required: a bar alone is not a name. |
| `valueText` | `string` | `""` | `value-text` | How assistive technology reads the value when a percentage is not the natural reading ("12.4 GB of 15 GB", "Strong"). Empty reads the platform's percentage. Where status means something, say it here too ("Almost full: 14.2 GB of 15 GB"). |

### States

`enabled`

### Accessibility

- Role: `meter`
- Labeling: label is the accessible name. valueText, when given, replaces the platform's percentage as the spoken value. A visible caption beside the meter is the product's composition; when it exists, use the same words as label. status is never announced: say in valueText what it means.
- Minimum target size: yes

- Not interactive: the meter takes no input and is never focusable (MTR-8), so the minimum target holds without extending anything.
- A meter shows where a value stands in a known range; it is not progress (WAI-ARIA APG, Meter: not for loading or percent completion) and not for a value without a meaningful maximum. A task under way is a progress indicator.
- The value is read from the boundary between indicator and track, so every indicator color — default and the four statuses — must stay at least 3:1 against comp.meter.track.color and the surface behind it (WCAG 1.4.11). In the default system each is above 4.6:1 in light and 7.3:1 in dark.
- Color is never the only sign of a status (WCAG 1.4.1): MTR-9.
- Web: the host carries role="meter" and the aria-value attributes; the native \<meter> element is not drawn, because its look cannot be styled in every engine.

### Behaviour

Requirements in EARS form, each with its stable id.

- **MTR-1** THE meter SHALL draw the indicator over the fraction (value − min) / (max − min) of the track, from the track's start: the inline-start of a bar, the top of a ring, turning clockwise.
- **MTR-2** THE meter SHALL draw a bar comp.meter.linear.thickness.medium high for medium (and so on for each size) across the full inline size its layout gives it, and a ring whose outer edge fills a comp.meter.circular.size.medium square with a stroke of comp.meter.circular.thickness.medium for medium (and so on for each size).
- **MTR-3** THE meter SHALL draw the track in comp.meter.track.color and the indicator over it in the indicator color of its status — comp.meter.indicator.color WHILE status is default, comp.meter.info.indicator.color WHILE it is info, comp.meter.success.indicator.color WHILE it is success, comp.meter.warning.indicator.color WHILE it is warning and comp.meter.error.indicator.color WHILE it is error; the ends of a bar's track and indicator SHALL be rounded by comp.meter.track.shape, clamped to half the thickness, and a ring's arcs SHALL have round caps unless comp.meter.track.shape is sys.shape.corner.none.
- **MTR-4** THE meter SHALL tell the indicator from the rest of its track as its separation names — WHILE separation is auto, as comp.meter.track.separation names; WHILE it is divider or gap, as that value names — and WHILE the separation is gap and value lies strictly between min and max SHALL leave comp.meter.track.gap of empty space, measured along the track, between the indicator and the rest of the track (on a ring, at both ends of the indicator's arc); WHILE it is divider THE meter SHALL draw the indicator on the track with nothing between them.
- **MTR-5** WHILE comp.meter.stop-indicator.size is above 0 THE meter SHALL draw at the end of a bar's track a round dot of that size, centred across the track, in the indicator color of its status; a ring SHALL draw none.
- **MTR-6** WHEN value, min, max or status changes after the first frame THE meter SHALL move the indicator's end to the new fraction over comp.meter.motion.value-duration with comp.meter.motion.value-easing, and change the indicator's color over the same duration; WHEN it changes again during that motion THE meter SHALL go on from the place and the color then shown. THE meter SHALL draw its first frame at its value and in its status's color without a motion, and WHERE reduced motion is requested SHALL move the end at once and keep the change of color as a fade over the same duration.
- **MTR-7** THE meter SHALL expose to assistive technology a meter — the platform's own meter where it has one, and otherwise an element with a value range and no busy state — named by label, with min and max as its range, the clamped value as its current value, and valueText as the value's text WHILE valueText is not empty; it SHALL NOT expose the progressbar role or a busy state, because nothing is under way.
- **MTR-8** THE meter SHALL NOT be focusable, SHALL take no input and SHALL NOT announce a change of its value or its status by itself; assistive technology SHALL read the current value when it reaches the meter.
- **MTR-9** THE meter SHALL expose nothing of status by itself: status SHALL change the indicator's color and nothing else, so that what the color means reaches every person only through the words the product gives in valueText, label or the text beside the meter.
- **MTR-10** IF value is below min or above max THE meter SHALL draw and expose it clamped to that range; IF value is not a finite number THE meter SHALL draw and expose min; IF max is not greater than min THE meter SHALL draw the track with no indicator and expose a current value of 0 in a range of 0 to 1; in each case debug builds SHALL report the values.
- **MTR-11** WHEN size names a value that the active design system does not have, or variant, separation or status a value the meter does not define, THE meter SHALL render with the default value's tokens (medium, linear, auto, default), and in debug builds report the unknown value.
- **MTR-12** WHERE the layout direction is right-to-left THE meter SHALL fill a bar from the right and draw its stop indicator at the left; a ring SHALL NOT be mirrored: it starts at the top and turns clockwise in every direction, as a clock does.
- **MTR-13** THE meter SHALL take its layout direction from where it is used and SHALL have no direction setting of its own; WHEN that direction changes while the meter is shown THE meter SHALL draw itself for the new direction at once, without a motion of its own, keeping its value.
- **MTR-14** WHILE label is empty THE meter SHALL still render, and in debug builds report that it has no accessible name.
- **MTR-15** WHERE the platform draws with a person's own forced colors THE meter SHALL draw its track as an edge sys.border.hairline wide in the platform's text color and its indicator filled in the platform's text color, whatever its status, so that the value is still read from where the indicator ends.
- **MTR-16** WHERE the platform has ARIA attributes (the web), the host carries its meter role (MTR-7) itself, so what the product writes on it is not moved, but for a name: WHILE label is not empty THE meter SHALL hold the product's aria-label and aria-labelledby on that host as their data-aria- twins, so that label is its whole name although the platform gives aria-labelledby the precedence, and WHEN label is emptied SHALL give them back as they were written; WHILE label is empty a name the product gives so — aria-label, or aria-labelledby naming an element — SHALL be its name and SHALL NOT be reported as having none in debug builds (MTR-14). WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaLabelledByElements) to null, THE meter SHALL take it away.

### Tokens

As written in the token documents (DTCG): an alias in braces names the token it takes its value from, which each design system resolves. On the web each token is a CSS custom property: `--tanrei` and the name after `tanrei`, joined by hyphens (`tanrei.comp.meter.…` → `--tanrei-comp-meter-…`).

#### `tanrei.comp.meter`

| Token | Type | Value |
| --- | --- | --- |
| `comp.meter.linear.thickness.small` | `dimension` | `{tanrei.comp.progress-indicator.linear.thickness.small}` |
| `comp.meter.linear.thickness.medium` | `dimension` | `{tanrei.comp.progress-indicator.linear.thickness.medium}` |
| `comp.meter.linear.thickness.large` | `dimension` | `{tanrei.comp.progress-indicator.linear.thickness.large}` |
| `comp.meter.circular.size.small` | `dimension` | `{tanrei.comp.progress-indicator.circular.size.small}` |
| `comp.meter.circular.size.medium` | `dimension` | `{tanrei.comp.progress-indicator.circular.size.medium}` |
| `comp.meter.circular.size.large` | `dimension` | `{tanrei.comp.progress-indicator.circular.size.large}` |
| `comp.meter.circular.thickness.small` | `dimension` | `{tanrei.comp.progress-indicator.circular.thickness.small}` |
| `comp.meter.circular.thickness.medium` | `dimension` | `{tanrei.comp.progress-indicator.circular.thickness.medium}` |
| `comp.meter.circular.thickness.large` | `dimension` | `{tanrei.comp.progress-indicator.circular.thickness.large}` |
| `comp.meter.track.color` | `color` | `{tanrei.comp.progress-indicator.track.color}` |
| `comp.meter.track.shape` | `dimension` | `{tanrei.comp.progress-indicator.track.shape}` |
| `comp.meter.track.separation` | `number` | `{tanrei.comp.progress-indicator.track.separation}` |
| `comp.meter.track.gap` | `dimension` | `{tanrei.comp.progress-indicator.track.gap}` |
| `comp.meter.stop-indicator.size` | `dimension` | `{tanrei.comp.progress-indicator.stop-indicator.size}` |
| `comp.meter.indicator.color` | `color` | `{tanrei.comp.progress-indicator.indicator.color}` |
| `comp.meter.info.indicator.color` | `color` | `{tanrei.sys.color.info}` |
| `comp.meter.success.indicator.color` | `color` | `{tanrei.sys.color.success}` |
| `comp.meter.warning.indicator.color` | `color` | `{tanrei.sys.color.warning}` |
| `comp.meter.error.indicator.color` | `color` | `{tanrei.sys.color.error}` |
| `comp.meter.motion.value-duration` | `duration` | `{tanrei.comp.progress-indicator.motion.value-duration}` |
| `comp.meter.motion.value-easing` | `cubicBezier` | `{tanrei.comp.progress-indicator.motion.value-easing}` |

## Guidelines

### When to use

Use a meter to show where a quantity stands in a range that has a real minimum and maximum: storage used of a quota, a battery’s charge, seats taken in a room, how strong a new password is. It shows a level; nothing is under way and nothing will finish.

For a task that is running — an upload, an import, a long save — use a [progress indicator](https://tanrei.chnotchy.com/components/progress-indicator/) instead. It looks the same, but assistive technology tells people that something is in progress, and a meter must not. A value with no meaningful maximum, such as a count of messages, is neither: show it as text.

### Choosing a variant

| Variant | Use for |
| --- | --- |
| Linear | A level that belongs to a page or a section: storage on a settings page, a quota in a card, a row in a list of plans. The bar fills the width its layout gives it. |
| Circular | Tight places and a level next to text: a battery in a status bar, a row of devices. The ring has a fixed size. |

### Choosing a size

| Size | Bar thickness / ring size in the default system | Use for |
| --- | --- | --- |
| Small | 2 / 16 | Dense rows, next to small text. |
| Medium | 4 / 24 | Most places. The default. |
| Large | 8 / 40 | The one level a view is about, such as the storage page of an account. |

The sizes, the track and the default color are the progress indicator’s, so a meter and a progress indicator side by side match. A design system can set the meter apart through its own tokens.

### Choosing a status

The meter does not decide what a value means. Whether more is better or worse is your product’s knowledge: storage nearly full is a warning, a password nearly at full strength is a success. Compute the status yourself and pass it in. The statuses have the names the tag and the snackbar use.

| Status | Use for |
| --- | --- |
| Default | A level that needs no comment. Most meters stay here. |
| Info | A level worth knowing about that asks for nothing, such as a quota that renews soon. |
| Success | A level that is where it should be, such as a strong password. |
| Warning | A level that needs attention soon, such as storage almost full or a low battery. |
| Error | A level that already stops something, such as storage that is full. |

A status changes the color of the indicator and nothing else. Say what it means in words as well: in the value text, and in the text beside the meter.

### Do

- Give it a label that says what is measured: “Storage used”, not “Meter”. If a caption is shown next to it, use the same words.
- Use a value text when a percentage is not the natural reading: “9.3 GB of 15 GB”, “37 of 50 seats”, “Strong”.
- When the status means something, put it in the value text too: “Almost full: 12.8 GB of 15 GB”.

### Don’t

- Don’t use it for a task that is running. That is a progress indicator.
- Don’t rely on the color of a status alone. Someone who cannot see the color must still learn that the storage is almost full.
- Don’t show a value outside the range. The meter draws and exposes it clamped to the range, so the number a person hears would not be the one you have.

## Accessibility

### What the component handles

- It exposes the meter role with the label as its name, the range, the current value (clamped to the range) and the value text when there is one. It never says that it is busy or that a task is in progress.
- It is never focusable, and it does not announce its changes. Assistive technology reads the current value when the person reaches it.
- A status is never announced on its own: it changes only the color.
- A change of value moves the end of the indicator and a change of status fades its color. When reduced motion is requested, the end moves at once and only the color fades.
- With forced colors, the track becomes a thin edge and the indicator is filled, both in the text color, whatever the status, so the value is still read where the indicator ends.
- An empty label, a value outside the range, a value that is not a number and an unknown `size`, `variant`, `separation` or `status` are reported in debug builds.

### What you need to do

- Say what a status means in words: in the value text, and in the text beside the meter.
- Announce a change that matters yourself, such as storage becoming full: a message in a live region or a notice the person will meet. The meter will not say it.
- Keep every indicator color — the default and the four statuses — at least 3:1 against the track and against the surface behind it if you change them. The value is read where the indicator meets the track. The default system holds all five above 4.6:1 in light and 7.3:1 in dark.
