# Meter

URL: https://ark-ui.com/docs/react/components/meter
LLM: https://ark-ui.com/llms.txt/components/meter

Displays a numeric value within a known range, such as disk usage or battery level.

---

---
id: meter
title: Meter
description: Displays a numeric value within a known range, such as disk usage or battery level.
status: new
---



## Anatomy

```tsx
<Meter.Root>
  <Meter.Label />
  <Meter.ValueText />
  <Meter.Track>
    <Meter.Indicator />
  </Meter.Track>
</Meter.Root>
```

## Meter or Progress?

A meter shows a measurement that stays within a range: storage used, battery remaining, a password's strength. A
progress bar shows how far a task has come and finishes when the task does. Use `Progress` for loading and uploads, and
`Meter` for everything that is a reading rather than a countdown to done.

## Examples

### Basic

Pass a `defaultValue` between `min` and `max` (`0` and `100` by default). The value is formatted as a percentage.

```tsx
import { Meter } from '@ark-ui/react/meter'
import styles from 'styles/meter.module.css'

export const Basic = () => (
  <Meter.Root className={styles.Root} defaultValue={24}>
    <Meter.Label className={styles.Label}>Storage used</Meter.Label>
    <Meter.ValueText className={styles.ValueText} />
    <Meter.Track className={styles.Track}>
      <Meter.Indicator className={styles.Indicator} />
    </Meter.Track>
  </Meter.Root>
)
```

### Value State

`low`, `high`, and `optimum` split the range into regions, the same way the HTML `<meter>` element does. The root,
track, and indicator expose the result as `data-state`: `optimal`, `suboptimal`, or `least-optimal`.

Put `optimum` below `low` when lower is better, as with server load, and above `high` when higher is better, as with
battery charge. The regions are exclusive at the optimal end, so with `high={80}` a value of exactly `80` is still
`suboptimal`.

```tsx
import { Meter } from '@ark-ui/react/meter'
import styles from 'styles/meter.module.css'

const servers = [
  { name: 'api-1', load: 32 },
  { name: 'api-2', load: 68 },
  { name: 'api-3', load: 91 },
]

export const ValueState = () => (
  <div className={styles.Stack}>
    {servers.map((server) => (
      <Meter.Root
        key={server.name}
        className={`${styles.Root} ${styles.Graded}`}
        defaultValue={server.load}
        low={50}
        high={80}
        optimum={0}
      >
        <Meter.Label className={styles.Label}>{server.name}</Meter.Label>
        <Meter.ValueText className={styles.ValueText} />
        <Meter.Track className={styles.Track}>
          <Meter.Indicator className={styles.Indicator} />
        </Meter.Track>
      </Meter.Root>
    ))}
  </div>
)
```

### Format Options

`formatOptions` goes to `Intl.NumberFormat`. Any style other than `percent` formats the value itself rather than its
percentage, which suits currency and units.

```tsx
import { Meter } from '@ark-ui/react/meter'
import styles from 'styles/meter.module.css'

export const FormatOptions = () => (
  <Meter.Root
    className={styles.Root}
    defaultValue={1240}
    max={2000}
    formatOptions={{ style: 'currency', currency: 'USD', maximumFractionDigits: 0 }}
  >
    <Meter.Label className={styles.Label}>Monthly budget</Meter.Label>
    <Meter.ValueText className={styles.ValueText} />
    <Meter.Track className={styles.Track}>
      <Meter.Indicator className={styles.Indicator} />
    </Meter.Track>
  </Meter.Root>
)
```

### Value Text

Use the `value` translation to describe the value in words. It sets both the rendered `ValueText` and the
`aria-valuetext` that screen readers announce.

```tsx
import { Meter } from '@ark-ui/react/meter'
import styles from 'styles/meter.module.css'

export const ValueText = () => (
  <Meter.Root
    className={styles.Root}
    defaultValue={3}
    max={5}
    translations={{ value: ({ value, max }) => `${value} of ${max} tasks` }}
  >
    <Meter.Label className={styles.Label}>Onboarding</Meter.Label>
    <Meter.ValueText className={styles.ValueText} />
    <Meter.Track className={styles.Track}>
      <Meter.Indicator className={styles.Indicator} />
    </Meter.Track>
  </Meter.Root>
)
```

### Min and Max

Set `min` and `max` for ranges that don't start at zero.

```tsx
import { Meter } from '@ark-ui/react/meter'
import styles from 'styles/meter.module.css'

export const MinMax = () => (
  <Meter.Root
    className={styles.Root}
    defaultValue={18}
    min={-10}
    max={40}
    formatOptions={{ style: 'unit', unit: 'celsius' }}
  >
    <Meter.Label className={styles.Label}>Temperature</Meter.Label>
    <Meter.ValueText className={styles.ValueText} />
    <Meter.Track className={styles.Track}>
      <Meter.Indicator className={styles.Indicator} />
    </Meter.Track>
  </Meter.Root>
)
```

### Vertical

Set `orientation="vertical"` to fill the indicator from the bottom. The indicator's height is set instead of its width.

```tsx
import { Meter } from '@ark-ui/react/meter'
import { useEffect, useState } from 'react'
import styles from 'styles/meter.module.css'

const channels = ['L', 'R', 'C', 'Sub']

const sample = () => channels.map(() => Math.round(20 + Math.random() * 80))

export const Vertical = () => {
  const [levels, setLevels] = useState(() => channels.map(() => 50))

  useEffect(() => {
    const id = setInterval(() => setLevels(sample()), 600)
    return () => clearInterval(id)
  }, [])

  return (
    <div className={styles.Levels}>
      {channels.map((channel, index) => (
        <Meter.Root
          key={channel}
          className={`${styles.Root} ${styles.Graded}`}
          orientation="vertical"
          value={levels[index]}
          low={60}
          high={85}
          optimum={30}
        >
          <Meter.Track className={styles.Track}>
            <Meter.Indicator className={styles.Indicator} />
          </Meter.Track>
          <Meter.Label className={styles.Label}>{channel}</Meter.Label>
        </Meter.Root>
      ))}
    </div>
  )
}
```

### Battery

A meter without a visible label needs an `aria-label`. Set `optimum` above `high` so a full battery reads as optimal and
an almost empty one as least optimal.

```tsx
import { Meter } from '@ark-ui/react/meter'
import { ZapIcon } from 'lucide-react'
import styles from 'styles/meter.module.css'

export const Battery = () => (
  <Meter.Root
    className={`${styles.Battery} ${styles.Graded}`}
    defaultValue={18}
    low={20}
    high={50}
    optimum={100}
    aria-label="Battery remaining"
  >
    <Meter.Track className={styles.Track}>
      <Meter.Indicator className={styles.Indicator} />
    </Meter.Track>
    <Meter.ValueText className={styles.ValueText} />
    <ZapIcon />
  </Meter.Root>
)
```

### Password Strength

A meter is a good fit for a password strength score. Map each score to a word with the `value` translation and let
`data-state` color the bar.

```tsx
import { Meter } from '@ark-ui/react/meter'
import { useState } from 'react'
import styles from 'styles/meter.module.css'

const score = (password: string) => {
  let points = 0
  if (password.length >= 8) points++
  if (password.length >= 12) points++
  if (/[a-z]/.test(password) && /[A-Z]/.test(password)) points++
  if (/\d/.test(password)) points++
  if (/[^a-zA-Z0-9]/.test(password)) points++
  return points
}

const labels = ['Too weak', 'Weak', 'Fair', 'Good', 'Strong', 'Very strong']

export const PasswordStrength = () => {
  const [password, setPassword] = useState('')
  const strength = score(password)

  return (
    <div className={styles.Field}>
      <input
        type="password"
        className={styles.Input}
        placeholder="Choose a password"
        aria-label="Password"
        value={password}
        onChange={(event) => setPassword(event.currentTarget.value)}
      />
      <Meter.Root
        className={`${styles.Root} ${styles.Graded}`}
        value={strength}
        max={5}
        low={2}
        high={3}
        optimum={5}
        translations={{ value: ({ value }) => labels[value] }}
      >
        <Meter.Label className={styles.Label}>Strength</Meter.Label>
        <Meter.ValueText className={styles.ValueText} />
        <Meter.Track className={styles.Track}>
          <Meter.Indicator className={styles.Indicator} />
        </Meter.Track>
      </Meter.Root>
      <span className={styles.Hint}>Use 12 or more characters with a mix of letters, numbers and symbols.</span>
    </div>
  )
}
```

### Context

Use `Context` to read `valueState` and render content that depends on it.

```tsx
import { Meter } from '@ark-ui/react/meter'
import styles from 'styles/meter.module.css'

const messages = {
  optimal: 'Plenty of space left',
  suboptimal: 'Running low on space',
  'least-optimal': 'Almost out of space',
}

export const Context = () => (
  <Meter.Root className={`${styles.Root} ${styles.Graded}`} defaultValue={86} low={60} high={85} optimum={0}>
    <Meter.Label className={styles.Label}>Disk usage</Meter.Label>
    <Meter.ValueText className={styles.ValueText} />
    <Meter.Track className={styles.Track}>
      <Meter.Indicator className={styles.Indicator} />
    </Meter.Track>
    <Meter.Context>
      {(meter) => (
        <span className={`${styles.Hint} ${styles.Status}`} data-state={meter.valueState}>
          {messages[meter.valueState]}
        </span>
      )}
    </Meter.Context>
  </Meter.Root>
)
```

### Root Provider

Use the `useMeter` hook with `RootProvider` to read the value and update it from outside the component.

```tsx
import { Meter, useMeter } from '@ark-ui/react/meter'
import button from 'styles/button.module.css'
import styles from 'styles/meter.module.css'

export const RootProvider = () => {
  const meter = useMeter({ defaultValue: 40 })

  return (
    <div className={styles.Stack}>
      <Meter.RootProvider className={styles.Root} value={meter}>
        <Meter.Label className={styles.Label}>Daily goal</Meter.Label>
        <Meter.ValueText className={styles.ValueText} />
        <Meter.Track className={styles.Track}>
          <Meter.Indicator className={styles.Indicator} />
        </Meter.Track>
      </Meter.RootProvider>
      <div className="hstack">
        <button type="button" className={button.Root} onClick={() => meter.setValue(meter.value - 10)}>
          -10
        </button>
        <button type="button" className={button.Root} onClick={() => meter.setValue(meter.value + 10)}>
          +10
        </button>
        <button type="button" className={button.Root} onClick={() => meter.setToMax()}>
          Complete
        </button>
      </div>
    </div>
  )
}
```

## Guides

### Styling

The indicator's `width` (or `height` when vertical) is set inline from the value, so the track only needs a size and
`overflow: hidden`. The root also exposes the fill as the `--percent` CSS variable.

```css
[data-meter-indicator][data-state='optimal'] {
  background: green;
}

[data-meter-indicator][data-state='suboptimal'] {
  background: orange;
}

[data-meter-indicator][data-state='least-optimal'] {
  background: red;
}
```

### Accessibility

The root has `role="meter"` with `aria-valuenow`, `aria-valuemin`, `aria-valuemax`, and `aria-valuetext` set from the
value and its formatting. `Label` names the meter; without one, pass an `aria-label` to the root. `ValueText` is hidden
from assistive technology, because `aria-valuetext` already announces the value.

## API Reference

### Props

### Root

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`defaultValue`**
Type: `number`
Required: false
Default Value: `undefined`
Description: The initial value of the meter when rendered.
Use when you don't need to control the value of the meter.

**`formatOptions`**
Type: `NumberFormatOptions`
Required: false
Default Value: `{ style: "percent" }`
Description: The options to use for formatting the value.

**`high`**
Type: `number`
Required: false
Default Value: `undefined`
Description: The lower numeric bound of the high end of the measured range.
Defaults to `max`.

**`id`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The unique identifier of the machine.

**`ids`**
Type: `Partial<{ label: string }>`
Required: false
Default Value: `undefined`
Description: The ids of the elements in the meter. Useful when the label lives outside
the machine and you need to control `aria-labelledby`.

**`locale`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The locale to use for formatting the value.

**`low`**
Type: `number`
Required: false
Default Value: `undefined`
Description: The upper numeric bound of the low end of the measured range.
Defaults to `min`.

**`max`**
Type: `number`
Required: false
Default Value: `100`
Description: The maximum allowed value of the meter.

**`min`**
Type: `number`
Required: false
Default Value: `0`
Description: The minimum allowed value of the meter.

**`onValueChange`**
Type: `(details: ValueChangeDetails) => void`
Required: false
Default Value: `undefined`
Description: Callback fired when the value changes.

**`optimum`**
Type: `number`
Required: false
Default Value: `undefined`
Description: The optimal numeric value. Combined with `low` and `high`, this decides
which region is preferred (HTML `<meter>` algorithm).
Defaults to the midpoint of `min` and `max`.

**`orientation`**
Type: `'horizontal' | 'vertical'`
Required: false
Default Value: `"horizontal"`
Description: The orientation of the element.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

**`translations`**
Type: `IntlTranslations`
Required: false
Default Value: `undefined`
Description: The localized messages to use.

**`value`**
Type: `number`
Required: false
Default Value: `undefined`
Description: The controlled value of the meter.

#### Data Attributes

**`data-meter-root`**: <uid>
**`data-value`**: The value of the item
**`data-state`**: "optimal" | "suboptimal" | "least-optimal"
**`data-orientation`**: The orientation of the meter

### Indicator

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

#### Data Attributes

**`data-meter-indicator`**: <uid>
**`data-orientation`**: The orientation of the indicator
**`data-state`**: "optimal" | "suboptimal" | "least-optimal"

### Label

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

#### Data Attributes

**`data-meter-label`**: <uid>
**`data-orientation`**: The orientation of the label

### RootProvider

#### Props

**`value`**
Type: `UseMeterReturn`
Required: true
Default Value: `undefined`
Description: undefined

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

### Track

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.

#### Data Attributes

**`data-meter-track`**: <uid>
**`data-orientation`**: The orientation of the track
**`data-state`**: "optimal" | "suboptimal" | "least-optimal"

### ValueText

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`render`**
Type: `ReactElement<unknown, string | JSXElementConstructor<any>> | RenderFn<EmptyState>`
Required: false
Default Value: `undefined`
Description: Render the part as a custom element, combining their props and behavior.

Pass an element to have the part's props merged into it, or a function to control the merge
yourself and read the part's state.
