# ProgressBar

> Progress bars show how far a task has got, such as an upload or a course, or that it is running with no known length.

Source: /docs/components/progress-bar

```tsx
import { ProgressBar } from '@aksornproject/aksara-ui'
```

## Basic progress bar

`value` is required. `label` is the first line and wraps when it is long. `valueLabel` sits on the right of the header, and `description` sits under the bar.

The bar fills its container with a 96px minimum. Set a width through `className`.

```tsx
import { ProgressBar } from '@aksornproject/aksara-ui'

export default function ProgressBarBasic() {
  return (
    <ProgressBar
      value={64}
      label="Disk usage"
      valueLabel="3.2 GB / 5 GB"
      description="1.8 GB remaining"
      className="max-w-[360px]"
    />
  )
}
```

## Variants

`variant` sets what the fill colour means. `accent` is the default. `positive`, `warning` and `negative` carry a status, and `neutral` carries none.

```tsx
import { ProgressBar } from '@aksornproject/aksara-ui'

export default function ProgressBarVariants() {
  return (
    <div className="flex w-full max-w-[360px] flex-col gap-spacing-24">
      <ProgressBar value={40} label="Course progress" variant="accent" />
      <ProgressBar value={100} label="Upload complete" variant="positive" />
      <ProgressBar value={85} label="Storage" variant="warning" />
      <ProgressBar value={95} label="Absences" variant="negative" />
      <ProgressBar value={25} label="Survey responses" variant="neutral" />
    </div>
  )
}
```

## Range and value text

`min` and `max` default to `0` and `100`, and the value is clamped to them. Screen readers hear `valueLabel` as the value. Without it they hear the percentage.

Changes to `value` ease the fill over 200ms.

```tsx
import { Button, ProgressBar } from '@aksornproject/aksara-ui'
import { useState } from 'react'

const TOTAL = 24

export default function ProgressBarRange() {
  const [marked, setMarked] = useState(9)

  return (
    <div className="flex w-full max-w-[360px] flex-col items-start gap-spacing-16">
      <ProgressBar
        value={marked}
        max={TOTAL}
        label="Marking essays"
        valueLabel={`${marked} of ${TOTAL}`}
      />
      <Button
        size="sm"
        variant="secondary-accent"
        disabled={marked === TOTAL}
        onClick={() => setMarked((count) => count + 1)}
      >
        Mark next essay
      </Button>
    </div>
  )
}
```

## Indeterminate

`value={null}` means the length is unknown. The fill slides across the track and stops under `prefers-reduced-motion`. `NaN` and `Infinity` also draw the indeterminate bar.

```tsx
import { ProgressBar } from '@aksornproject/aksara-ui'

export default function ProgressBarIndeterminate() {
  return (
    <ProgressBar
      value={null}
      label="Importing class list"
      description="This can take a minute."
      className="max-w-[360px]"
    />
  )
}
```

## Track only

With no `label`, `valueLabel` or `description`, the root holds only the track. It still needs a name, from `aria-label` or from `aria-labelledby`, here the row header.

```tsx
import { ProgressBar, typography } from '@aksornproject/aksara-ui'

export default function ProgressBarTrack() {
  return (
    <table className={`${typography('body-regular')} text-color-text-primary`}>
      <tbody>
        <tr>
          <th
            id="unit-1"
            scope="row"
            className="pr-spacing-16 text-left font-normal"
          >
            Unit 1: Fractions
          </th>
          <td>
            <ProgressBar
              value={80}
              aria-labelledby="unit-1"
              className="w-[120px]"
            />
          </td>
        </tr>
        <tr>
          <th
            id="unit-2"
            scope="row"
            className="pr-spacing-16 text-left font-normal"
          >
            Unit 2: Decimals
          </th>
          <td>
            <ProgressBar
              value={35}
              aria-labelledby="unit-2"
              className="w-[120px]"
            />
          </td>
        </tr>
      </tbody>
    </table>
  )
}
```

## Accessibility

- The root has `role="progressbar"` with `aria-valuenow`, `aria-valuemin` and `aria-valuemax`. An indeterminate bar has no `aria-valuenow`.
- A bar needs exactly one name. Pass a visible `label`, or `aria-label` or `aria-labelledby` when nothing shows. TypeScript rejects a nameless bar and a `label` paired with another name.
- `valueLabel` is hidden from screen readers and announced as `aria-valuetext` instead. Without it they hear the percentage, or "indeterminate progress".
- `description` is linked with `aria-describedby`, after any ids you pass.

## API

### ProgressBar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `ProgressBarText` |  | The visible label, the first line. It wraps; the value label stays on the right. |
| `aria-label` | `string` |  | The name when no visible `label` shows. |
| `aria-labelledby` | `string` |  | The id of the element that names the bar when no visible `label` shows. |
| `value` (required) | `number \| null` |  | The progress. `null` shows an indeterminate bar. Values outside `min`..`max` are clamped. |
| `min` | `number` |  | Defaults to `0`. |
| `max` | `number` |  | Defaults to `100`. |
| `variant` | `"accent" \| "negative" \| "neutral" \| "positive" \| "warning"` | `'accent'` | Defaults to `'accent'`. |
| `valueLabel` | `string` |  | The text on the right of the header, such as `'3.2 GB / 5 GB'`. Screen readers announce it as the value (`aria-valuetext`). Without it they hear the percentage. |
| `description` | `ProgressBarText` |  | Helper text, the line under the bar. Linked with `aria-describedby` after any ids you pass. |
| `className` | `string` |  | Merged with the token-aware `cn()`. The bar fills its container; set a width here. |