import { Meter, MeterTrack, Text } from '@vega-ui/react';
<div>
<div>
<Text id='preview-meter-label' size={2} fontWeight={500}>Cloud storage</Text>
<Text size={2}>36 of 50 GB</Text>
</div>
<Meter value={36} max={50} valueText='36 GB of 50 GB used' aria-labelledby='preview-meter-label' fullWidth>
<MeterTrack />
</Meter>
</div>- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 113
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- Oklab and OkLCh — comes from the design tokens
- :where()
Summary
Meter shows a bounded quantitative value, typically without the temporal progress semantics of a progress bar. It is the descriptive-measurement bar when the UI should read as level, score, capacity, or quality rather than task completion.
Imports
import {
Meter,
MeterTrack,
type MeterProps,
type MeterSize,
type MeterVariant,
} from '@vega-ui/react';Exported Types
MeterPropsMeterTrackPropsMeterSizeMeterVariant
Minimal Composition
max defaults to 1, so a fraction like 0.72 needs no max; pass max for any other scale (value={36} max={50}). The meter has no visible label of its own: give it aria-label or aria-labelledby.
import { Meter, MeterTrack } from '@vega-ui/react';
export default function MinimalComposition() {
return (
<Meter value={0.72} valueText='72%' aria-label='Disk usage' fullWidth>
<MeterTrack />
</Meter>
);
}Required Parts
Meter: root meter surfaceMeterTrack: visible measured-fill track
Optional Parts
- No extra public parts beyond root and track
Composition Order
MeterMeterTrack
Variants
- Sizes:
sm,md,lg - Variants:
primary,secondary - States: neutral, good, warn, bad via thresholds
Edge Cases
Meteris for bounded measurement, not async completion.- Threshold props like
low,high, andoptimumcan shift the semantic color state. - Surrounding copy should explain the measured value and range.
- If the measurement is actually composed of several meaningful parts,
MeterStackis usually clearer than one continuous bar.
Common Mistakes
- Using
Meterfor loading or async work. - Showing a value without explaining the range or meaning.
- Ignoring threshold semantics when they matter to interpretation.
Examples
Basic
Basic: storage usage with a visible summary
A meter on a real scale: max is the plan limit, so value is passed in gigabytes, not as a fraction. The visible heading names the meter through aria-labelledby, and valueText repeats the human-readable summary for screen readers instead of a bare number.
import { Meter, MeterTrack, Text } from '@vega-ui/react';
import { useId } from 'react';
export default function StorageUsage() {
const labelId = useId();
const usedGb = 36.2;
const limitGb = 50;
const summary = `${usedGb} GB of ${limitGb} GB used`;
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 'var(--spacing-8)' }}>Controlled/Stateful
Controlled/Stateful: battery level with threshold zones
The parent owns the level and passes it to the meter together with low, high and optimum. The track turns red, amber or green on its own, while the same thresholds drive a text status linked through aria-describedby, so the meaning never relies on color.
import { Button, Meter, MeterTrack, Text } from '@vega-ui/react';
import { useId, useState } from 'react';
export default function BatteryLevel() {
const labelId = useId();
const statusId = useId();
const [level, setLevel] = useState(64);
let status = 'Battery is fine';
if (level < 20) {
status = 'Battery is low — connect the charger';
} else if (level > 80) {Form/Integration
Form/Integration: character limit under a textarea
The meter follows the length of a TextArea inside a real form. optimum={0} makes short text the good zone, so the bar turns amber near the limit and red at it; maxLength enforces the hard cap and the remaining count is linked to the field. The submit handler reads the value with FormData, and reset clears the state.
import { Button, HelperText, Label, Meter, MeterTrack, TextArea } from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
export default function BioLengthMeter() {
const fieldId = useId();
const counterId = useId();
const limit = 160;
const [bio, setBio] = useState('Product designer in Lisbon. I write about design systems and accessible forms.');
const [saved, setSaved] = useState<string | null>(null);
const left = limit - bio.length;
Layout/Overlay
Layout/Overlay: plan usage card
Several meters in a card, one list item each, every one labelled by its own row heading. Lower is better here (optimum={0}), so a resource close to its limit stands out without extra code.
import { Card, Heading, Meter, MeterTrack, Text } from '@vega-ui/react';
import { useId } from 'react';
const usage = [
{ id: 'seats', label: 'Seats', used: 18, limit: 20, unit: 'seats' },
{ id: 'storage', label: 'Storage', used: 412, limit: 500, unit: 'GB' },
{ id: 'minutes', label: 'Build minutes', used: 1240, limit: 3000, unit: 'min' },
];
export const locales = ['en-US', 'ru-RU', 'ar-EG', 'he-IL'];
export default function MeterInsideACard({ locale = 'en-US' }: { locale?: string }) {Error
Error: quota exceeded
The meter clamps the fill at max, so an overflow cannot be seen in the bar alone. When usage passes the limit, the example renders an error HelperText, links it to the meter with aria-describedby and says exactly how far over the limit the account is.
import { Button, HelperText, Meter, MeterTrack, Text } from '@vega-ui/react';
import { useId, useState } from 'react';
export const locales = ['en-US', 'ru-RU', 'ar-EG', 'he-IL'];
export default function QuotaExceeded({ locale = 'en-US' }: { locale?: string }) {
const labelId = useId();
const errorId = useId();
const limit = 10000;
const [requests, setRequests] = useState(8700);
const format = new Intl.NumberFormat(locale);Edge
Edge: non-zero minimum and an optimum in the middle
A temperature scale from 10 to 35 °C with the comfortable zone in the middle: values below low or above high are both bad. min shifts the start of the track, and the verdict is printed next to each value.
import { Meter, MeterTrack, Text } from '@vega-ui/react';
import { useId } from 'react';
const rooms = [
{ id: 'living', name: 'Living room', celsius: 21.5 },
{ id: 'bedroom', name: 'Bedroom', celsius: 18.2 },
{ id: 'server', name: 'Server closet', celsius: 29.8 },
];
export default function RoomTemperature() {
const prefix = useId();
Anatomy
Overview
Meter is a compound component because the root meter role and the visible track share one bounded measurement model.
Required Parts
Meter
Required. Owns range values, thresholds, variant, size, and accessibility attributes.
MeterTrack
Required. Renders the visible measured-fill amount.
Optional Parts
Meter does not expose additional public parts beyond the root and track.
Composition Order
MeterMeterTrack
Valid Composition Patterns
import { Meter, MeterTrack } from '@vega-ui/react';
export default function MinimalComposition() {
return (
<Meter value={0.72} valueText='72%' aria-label='Disk usage' fullWidth>
<MeterTrack />
</Meter>
);
}Invalid Composition Patterns
Meter used for ongoing async completion
If the value represents temporal completion, Progress is the better pattern.
Threshold-based colors without explaining what the range means
Users may see semantic color shifts but not understand the measurement.