# Progress

A bar that fills, or sweeps when the end is unknown.

- Group: Feedback
- Import: `import { Progress } from '@misoto22/folio'`
- Page: https://ui.misoto22.com/components/progress/
- Related: spinner

## Anatomy

- **Track** (required) — The Radix root: 4px of --stone at the pill radius, carrying role="progressbar" and aria-label from label. It is full width by default, so it takes the measure of whatever column it is dropped into.
- **Fill** — The indicator, in --accent, sized by width rather than a translate so it grows from the inline start in a right-to-left document too. Present only when value is a number.
- **Sweep** — What replaces the fill when value is null: a quarter-width --accent bar travelling the track on transform alone, mirrored under rtl so it never reads as progress running backwards. Under prefers-reduced-motion it stops where it is drawn rather than filling the track.
- **Value row** — label on the left, a tabular-nums percentage on the right, above the track. It renders only when showValue is set and value is a number, which makes it the only place label is ever visible.
- **Column** — The flex wrapper holding the row and the track. className lands here; every other prop is forwarded to the Radix root instead.

## Best practices

### Do

- Pass showValue on any determinate bar. It is the only thing that puts label on screen — without it the name exists solely as aria-label, and a sighted reader is left with an unlabelled 4px rule and no number.
- Switch value back to null the moment the estimate stops being real. The fill transitions its width over --duration-slow, so a number that revises downward animates backwards and the reader watches progress undo itself.
- Set max to the real total rather than converting to a percentage yourself. The width and aria-valuemax come off the same number, so the picture and the announcement cannot drift — but a value past the ceiling still clamps, and an underestimated total parks the bar at full for the rest of the operation rather than admitting the estimate was wrong.

### Don’t

- Do not pass a max that is not a positive number. Radix refuses it, prints its own warning and falls back to 100, and the width falls back with it — so value={40} paints and announces forty per cent of a ceiling nobody chose.
- Do not read the resting sweep as a position. Under prefers-reduced-motion it stops at a quarter of the track, which is what a determinate bar at 25% looks like — the announcement is the only thing that tells the two apart, and it is the reason omitting value matters.
- className styles the column, not the track, so a height utility passed that way stretches the wrapper and leaves the 4px bar exactly where it was.

## Accessibility

- Omitting value drops aria-valuenow, so a screen reader hears “indeterminate” rather than a number that is a guess.
- label is required — a bare bar announces nothing.
- The width is computed from value and max, the same pair Radix announces as aria-valuenow and aria-valuemax, so what is drawn and what is said cannot disagree.

## Progress

A bar that fills, or sweeps when the end is unknown. Flat: a track in `--stone`, a fill in `--ink`. The White Reset has no gradient and no glow, so the only thing carrying the reading is the boundary between the two.

### Props

- `value` — `number | null` default `null`. 0 to `max`, which is 100 unless you say otherwise. Omit (or pass `null`) when the duration is genuinely unknown — the bar then sweeps instead of filling, and Radix drops `aria-valuenow` so a screen reader is told "indeterminate" rather than a number that is a guess.
- `max` — `number`. What `value` is measured against. 100 by default. The width is computed from it as well as announced from it. It used to be only announced: `max` reached Radix through `...rest` while the width was `value` clamped to 100, so `max={500}` with `value={100}` painted a full bar and told a screen reader "100 of 500". A max that is not a positive number is refused the way Radix refuses it — Radix warns, and both fall back to 100 rather than drawing one picture and announcing another.
- `label` (required) — `string`. Names what is progressing. Required: a bare bar announces nothing.
- `showValue` — `boolean` default `false`. Prints the percentage above the bar. Only meaningful when `value` is set.

Also accepts: `Omit<ComponentProps<typeof ProgressPrimitive.Root>, 'value' | 'max'>`.

## Example — determinate

```tsx
import { Progress } from '@misoto22/folio'

<div className="w-full max-w-sm">
  <Progress value={62} label="Uploading photos" showValue />
</div>
```

## Example — indeterminate

```tsx
import { Progress, Text } from '@misoto22/folio'

<div className="flex w-full max-w-sm flex-col gap-3">
  <Text size="sm" tone="strong">
    Rebuilding the search index
  </Text>
  <Progress label="Rebuilding the search index" />
  <Text size="xs" tone="muted">
    Started 40 seconds ago. No estimate yet.
  </Text>
</div>
```

## Example — bytes into percent

```tsx
import { Progress, Text } from '@misoto22/folio'

<div className="flex w-full max-w-sm flex-col gap-2">
  <Progress
    value={Math.round((18.2 / 29.4) * 100)}
    label="Uploading footage.mov"
    showValue
  />
  <Text size="xs" tone="muted">
    18.2 MB of 29.4 MB
  </Text>
</div>
```
