# Progress

Shows how far a task with a known length has got, such as an import or a setup.

- **Import:** `import { Progress } from "@booleanpress/ui/progress"`
- **Radix Progress:** <https://www.radix-ui.com/primitives/docs/components/progress>
- **Page:** <https://ui.booleanpress.com/components/progress> · @booleanpress/ui 0.1.0

## Usage

Pass `value`, a number from 0 to 100, and a name.

```tsx
import { Progress } from "@booleanpress/ui/progress"

export function ImportProgress() {
  return <Progress value={60} aria-label="Import progress" />
}
```

The bar is controlled only: it shows the `value` you pass and you update it as the work goes. The indicator slides in from the inline start, and `value` reaches the Radix root, so the bar reports `aria-valuenow` and `data-state` (`loading` below 100, `complete` at 100). Stock leaves `value` on the indicator only, which makes the bar read as indeterminate to a screen reader.

Show the number or the step beside the bar, in text: the bar alone is not a precise reading. For a wait of unknown length, use a spinner.

## Examples

### Basic

A bar at 60 %, named with `aria-label`.

```tsx
import { Progress } from "@booleanpress/ui/progress"

export default function ProgressBasic() {
  return <Progress value={60} aria-label="Import progress" className="w-full max-w-sm" />
}
```

### With a label

A visible label and a count, tied to the bar with `aria-labelledby`.

```tsx
import { Label } from "@booleanpress/ui/label"
import { Progress } from "@booleanpress/ui/progress"

export default function ProgressWithLabel() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <div className="flex items-center justify-between text-sm">
        <Label id="import-label">Importing people</Label>
        <span className="text-muted-foreground tabular-nums">420 of 700</span>
      </div>
      <Progress value={60} aria-labelledby="import-label" />
    </div>
  )
}
```

### Controlled

The page owns the value and moves it a step at a time.

```tsx
import { useState } from "react"
import { Button } from "@booleanpress/ui/button"
import { Progress } from "@booleanpress/ui/progress"

export default function ProgressControlled() {
  const [value, setValue] = useState(25)

  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <Progress value={value} aria-label="Setup progress" />
      <div className="flex items-center justify-between gap-4">
        <span className="text-sm text-muted-foreground tabular-nums">Step {value / 25} of 4</span>
        <Button size="sm" variant="outline" disabled={value === 100} onClick={() => setValue((v) => Math.min(100, v + 25))}>
          Next step
        </Button>
      </div>
    </div>
  )
}
```

### Complete

At 100 the bar reports `complete`; the page says what finished.

```tsx
import { CircleCheckIcon } from "lucide-react"
import { Progress } from "@booleanpress/ui/progress"

export default function ProgressComplete() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Progress value={100} aria-label="Import progress" />
      <p className="flex items-center gap-1.5 text-sm text-muted-foreground">
        <CircleCheckIcon className="size-4 text-success" aria-hidden="true" />
        700 people imported
      </p>
    </div>
  )
}
```

## Accessibility

**Semantics.** Radix renders a `div` with `role="progressbar"`, `aria-valuemin="0"`, `aria-valuemax="100"` and `aria-valuenow` (this package's patch), plus `data-state` and `data-value`.

**Labels.** Name every bar, with `aria-label` or `aria-labelledby`. Put the number in visible text beside it as well.

**Focus.** A progress bar takes no focus and has no keyboard behaviour, so it has no keyboard rows.

**Known limits.**

- A progress bar is not a live region: screen readers do not announce each change. Announce milestones ("Import finished") in a live region the page owns.
- With no `value` the bar is `indeterminate` (no `aria-valuenow`), but the indicator is then empty and nothing animates, so it looks like 0 %. Use a spinner for work with no known length.
- The slide is a `transition-all`. `theme.css` ends it under `prefers-reduced-motion`, and the bar then jumps to its value.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### Progress

Renders Radix Progress.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `getValueLabel` | `((value: number, max: number) => string)` |  | Returns the text for `aria-valuetext` from the value and the max, such as "420 of 700". |
| `max` | `number` |  | The top of the range for Radix's state (`complete`) and `aria-valuemax`. The indicator's position assumes 100, so leave it. |
| `value` | `number \| null` |  | The progress from 0 to 100. Also sets `aria-valuenow` and the indicator's position. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="progress"` (Progress).

## Theming

The track is `--primary` at 20 %, the indicator `--primary`. Override either with `className` (on the track) or with a child selector.

| Token | Used for |
| --- | --- |
| `--primary` | background |
