# Meter

Shows a measurement within a known range, such as storage or budget used. It doesn't show task progress.

```tsx
// demo.tsx
import {Grid, Meter, Stack} from '@earendil-works/elanor-react';

export default function MeterDemo() {
  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 6, medium: 4}}>
        <Meter.Root value={24}>
          <Stack gap="xSmall" align="stretch">
            <Stack direction="row" gap="auto">
              <Meter.Label>Storage used</Meter.Label>
              <Meter.Value />
            </Stack>
            <Meter.Track>
              <Meter.Indicator />
            </Meter.Track>
          </Stack>
        </Meter.Root>
      </Grid.Item>
    </Grid.Root>
  );
}
```

## Usage

```tsx
import {Meter} from '@earendil-works/elanor-react';

<Meter.Root value={24}>
  <Meter.Label>Storage used</Meter.Label>
  <Meter.Value />
  <Meter.Track>
    <Meter.Indicator />
  </Meter.Track>
</Meter.Root>;
```

Meter adds no layout; arrange its parts with [Stack](/components/stack.md). Without a visible label, name the meter with `aria-label` on `Meter.Root`.

## Semantic

`warning` and `negative` flag a value near or past a limit. Choose the thresholds in your application.

```tsx
// semantic.tsx
import {Grid, Meter, Stack} from '@earendil-works/elanor-react';
import type {MeterSemantic} from '@earendil-works/elanor-react';

const meters: ReadonlyArray<{label: string; value: number; semantic: MeterSemantic}> = [
  {label: 'Neutral', value: 40, semantic: 'neutral'},
  {label: 'Warning', value: 85, semantic: 'warning'},
  {label: 'Negative', value: 100, semantic: 'negative'},
];

export default function MeterSemanticExample() {
  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 6, medium: 4}}>
        <Stack gap="large" align="stretch">
          {meters.map(({label, value, semantic}) => (
            <Meter.Root key={label} value={value} semantic={semantic}>
              <Stack gap="xSmall" align="stretch">
                <Stack direction="row" gap="auto">
                  <Meter.Label>{label}</Meter.Label>
                  <Meter.Value />
                </Stack>
                <Meter.Track>
                  <Meter.Indicator />
                </Meter.Track>
              </Stack>
            </Meter.Root>
          ))}
        </Stack>
      </Grid.Item>
    </Grid.Root>
  );
}
```

## Thickness

The track is `semantic.dimension.size.xxSmall` (8px) tall. For a thin meter, give `Meter.Track` a class that sets `block-size` to `semantic.dimension.size.xxxSmall` (4px); the indicator follows.

```tsx
// thickness.tsx
import {Grid, Meter, Stack} from '@earendil-works/elanor-react';

import styles from './meter.module.css';

export default function MeterThickness() {
  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 6, medium: 4}}>
        <Meter.Root value={60}>
          <Stack gap="xSmall" align="stretch">
            <Stack direction="row" gap="auto">
              <Meter.Label>Seats used</Meter.Label>
              <Meter.Value />
            </Stack>
            <Meter.Track className={styles.thin}>
              <Meter.Indicator />
            </Meter.Track>
          </Stack>
        </Meter.Root>
      </Grid.Item>
    </Grid.Root>
  );
}
```

```css
/* meter.module.css */
.thin {
  block-size: var(--semantic-dimension-size-xxx-small);
}
```

## Custom range

`value` is a percentage by default; set `min` and `max` for other ranges. `format` formats the value, and `getAriaValueText` gives screen readers the same text.

```tsx
// custom-range.tsx
import {Grid, Meter, Stack} from '@earendil-works/elanor-react';

const used = 40;
const limit = 50;
const format = {style: 'currency', currency: 'USD', maximumFractionDigits: 0} as const;
const limitText = new Intl.NumberFormat('en-US', format).format(limit);

export default function MeterCustomRange() {
  return (
    <Grid.Root>
      <Grid.Item span={{base: 'full', xSmall: 6, medium: 4}}>
        <Meter.Root
          value={used}
          max={limit}
          format={format}
          locale="en-US"
          getAriaValueText={(formatted) => `${formatted} of ${limitText}`}
        >
          <Stack gap="xSmall" align="stretch">
            <Stack direction="row" gap="auto">
              <Meter.Label>Monthly budget</Meter.Label>
              <Meter.Value>{(formatted) => `${formatted} of ${limitText}`}</Meter.Value>
            </Stack>
            <Meter.Track>
              <Meter.Indicator />
            </Meter.Track>
          </Stack>
        </Meter.Root>
      </Grid.Item>
    </Grid.Root>
  );
}
```

## API

### `Meter.Root`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `semantic` | `'neutral' \| 'warning' \| 'negative'` | `'neutral'` | Indicator color. `warning` and `negative` flag a value near or past a limit. |

Also accepts Base UI [`Meter.Root`](https://base-ui.com/react/components/meter.md) props.

### `Meter.Label`

Accepts Base UI [`Meter.Label`](https://base-ui.com/react/components/meter.md) props.

### `Meter.Value`

Accepts Base UI [`Meter.Value`](https://base-ui.com/react/components/meter.md) props.

### `Meter.Track`

Accepts Base UI [`Meter.Track`](https://base-ui.com/react/components/meter.md) props.

### `Meter.Indicator`

Accepts Base UI [`Meter.Indicator`](https://base-ui.com/react/components/meter.md) props.
