FairGarden Design

Meter

A Base UI Meter for a measured value in a known range: storage used, strength, a level on an ordinal scale. Never use a meter for an unbounded value.

import { Meter, MeterPanel } from '@fairgarden-private/design/components/Meter'

Meters are ink only: track edges, indicators, cells, ticks and labels all use the scope's step-12 ink, and the track interior shows the ground. No part uses the secondary scale, so a meter never reads as a chart series. The value is always in text.

Kinds in a meter panel

Kinds in a meter panel

bar is the default: an outlined 8 px track with a solid indicator. threshold draws a 12 px tick across the track at a limit, and nearing or passing it is a status glyph plus words (▲ "Near limit", ◆ "Over limit"), never an indicator color swap. steps draws one outlined cell per unit. ordinal is a 1 px scale with a tick and caps label per stop (stops); the active stop is bold, one step larger and pointed to by an outlined triangle. ring is a 2 px arc beside its value.

MeterPanel stacks meters and is their container: label, track and value share one row once the panel is at least 768 px wide, and stack below that. Ordinal labels wrap in narrow panels, and all of them are kept.

Photo storage
Near limitx
Trail difficulty
x
Volunteer shifts
x
Seed bankx
MeterKinds.tsx
'use client'

import { Meter, MeterPanel } from '@fairgarden-private/design/components/Meter'

const gigabytes = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 })

/** A meter panel: storage near its limit, an ordinal difficulty scale, segmented cells and a ring. */
export function MeterKinds() {
  return (
    <MeterPanel>
      <Meter
        label="Photo storage"
        value={9.4}
        max={10}
        threshold={9}
        formatValue={(_formatted, value) => `${gigabytes.format(value)} of 10 GB`}
        status="warning"
        statusText="Near limit"
      />
      <Meter kind="ordinal" label="Trail difficulty" value={2} stops={['Easy', 'Moderate', 'Hard', 'Strenuous']} />
      <Meter
        kind="steps"
        label="Volunteer shifts"
        value={3}
        max={5}
        formatValue={(_formatted, value) => `${value} of 5`}
      />
      <Meter kind="ring" label="Seed bank" value={72} />
    </MeterPanel>
  )
}

In print

Linear and segmented values print as a 45° hatch inside their outline, closed by an edge at the value, with the numeral, threshold tick and status words kept. Rings and ordinal scales print as line, with the active label in bold.

API Reference

Meter

A Base UI Meter drawn in ink only: every part is --primary12 on the ground; the value is always in text. In print, linear and segmented indicators turn to a 45° hatch inside their outline, closed by an edge at the value; rings and ordinal scales print as line.

PropTypeDescription
label
React.ReactNode | undefined

The label, in caps (type-label); author it in sentence case.

value
number | undefined

The measured value, between min and max; for ordinal, the index of the active stop.

formatValue
| ((
    formattedValue: string,
    value: number,
  ) => React.ReactNode)
| undefined

Formats the visible value (type-data): “62%", “4.2 of 10 GB”. Default: the Base UI formatted value.

kind
'bar' | 'steps' | 'ordinal' | 'ring' | null | undefined

bar (default): an outlined 8 px track with a solid indicator. steps: one outlined 12 px cell per unit (maxmin cells). ordinal: a 1 px track with a tick and a caps label per stop, the active stop bold, larger and pointed to. ring: a 2 px arc beside its value.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: track, indicator, ticks, stops and text. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale: unused, so a meter never reads as a chart series. Never defaulted.

status
MeterStatus | undefined

The status beside the value; always with statusText.

statusText
React.ReactNode | undefined

The status word: “Near limit”, “Over limit”. In a form it is also the Field description.

stops
React.ReactNode[] | undefined

ordinal only: the stop labels in order (author them in sentence case; CSS sets the caps). min becomes 0 and max the last index.

threshold
number | undefined

A limit, drawn as a --ds-space-12 tick at --border-size-2 across the track (bar).

MeterPanel

A meter or scale panel: stacks its meters and is the meter-panel container, so each meter sets label | track | value in one row from 768 px of the panel’s width, and stacks below it.

Additional types

meter
type meter = meter
MeterPanelProps

Props for MeterPanel: div props.

type MeterPanelProps = React.ComponentPropsWithRef<'div'>
MeterProps

Props for Meter: Base UI Meter Root props plus the kind and color axes, the label, stops, threshold and status.

type MeterProps = {
  /** The measured value, between `min` and `max`; for `ordinal`, the index of the active stop. */
  value: number;
  /**
   * `bar` (default): an outlined 8 px track with a solid indicator. `steps`:
   * one outlined 12 px cell per unit (`max` − `min` cells). `ordinal`: a
   * 1 px track with a tick and a caps label per stop, the active stop bold,
   * larger and pointed to. `ring`: a 2 px arc beside its value.
   */
  kind?: 'bar' | 'steps' | 'ordinal' | 'ring' | null;
  /**
   * Primary Radix scale: track, indicator, ticks, stops and text. Never
   * defaulted; omitted, it inherits the scope [D133].
   */
  primary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  /** Secondary Radix scale: unused, so a meter never reads as a chart series. Never defaulted. */
  secondary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
  /** The label, in caps (`type-label`); author it in sentence case. */
  label?: React.ReactNode;
  /** Formats the visible value (`type-data`): "62%", "4.2 of 10 GB". Default: the Base UI formatted value. */
  formatValue?: (formattedValue: string, value: number) => React.ReactNode;
  /**
   * `ordinal` only: the stop labels in order (author them in sentence case;
   * CSS sets the caps). `min` becomes 0 and `max` the last index.
   */
  stops?: React.ReactNode[];
  /** A limit, drawn as a --ds-space-12 tick at --border-size-2 across the track (`bar`). */
  threshold?: number;
  /** The status beside the value; always with `statusText`. */
  status?: MeterStatus;
  /** The status word: "Near limit", "Over limit". In a form it is also the Field description. */
  statusText?: React.ReactNode;
}
meterStatus

The status part: a §1.5.4 glyph plus a word, in the status scale [D129].

type meterStatus = meterStatus
MeterStatus

A status beside the value: warning ▲ “Near limit”, danger ◆ “Over limit”, success ●.

type MeterStatus = 'success' | 'warning' | 'danger'

Specification: DESIGN-SYSTEM.md §10.18 (progress and meter) and §8.9 (meters, scales and data progress).