# Progress

> Progress: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/progress](https://askrjs.com/docs/components/progress)

Status: stable. Packages: @askrjs/ui/progress, @askrjs/ui/progress-circle, @askrjs/themes/progress.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
import { Progress } from '@askrjs/themes/components';

<Progress value={uploaded()} max={total()} aria-label="Upload progress" />
```

## Published props

Generated from the TypeScript declarations shipped by the installed package.

### `ProgressIndicatorAsChildProps`

Import from `@askrjs/ui/progress`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `ProgressIndicatorProps`

Import from `@askrjs/ui/progress`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `ProgressOwnProps`

Import from `@askrjs/ui/progress`.

- `children?: unknown;`
- `getValueLabel?: ((value: number | null, max: number) => string) | undefined;`
- `id?: string | undefined;`
- `max?: number | undefined;`
- `value?: number | null | undefined;`

### `ProgressProps`

Import from `@askrjs/ui/progress`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `getValueLabel?: ((value: number | null, max: number) => string) | undefined;`
- `id?: string | undefined;`
- `max?: number | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `value?: number | null | undefined;`

### `ProgressCircleIndicatorAsChildProps`

Import from `@askrjs/ui/progress-circle`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `ProgressCircleIndicatorProps`

Import from `@askrjs/ui/progress-circle`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `ProgressCircleOwnProps`

Import from `@askrjs/ui/progress-circle`.

- `children?: unknown;`
- `getValueLabel?: ((value: number | null, max: number) => string) | undefined;`
- `id?: string | undefined;`
- `max?: number | undefined;`
- `value?: number | null | undefined;`

### `ProgressCircleProps`

Import from `@askrjs/ui/progress-circle`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `getValueLabel?: ((value: number | null, max: number) => string) | undefined;`
- `id?: string | undefined;`
- `max?: number | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `value?: number | null | undefined;`

## Purpose

Progress and ProgressCircle communicate how far along a task with a known endpoint is — a linear bar or a ring, picked by layout rather than meaning. Neither is interactive: they render a value, not a control, so if what you actually have is an unknown-duration wait, use Spinner instead of forcing a fake percentage into either of these.

## Install and import

The headless primitives live at `@askrjs/ui/progress` (linear) and `@askrjs/ui/progress-circle` (circular). The themed layer only ships a dedicated `@askrjs/themes/progress` subpath for the linear bar — ProgressCircle is still themed and re-exported, but you pull it from the full `@askrjs/themes/components` barrel rather than its own subpath.

## Live examples

The example puts the linear and circular variants side by side: `value` drives the fill width on one and the ring's stroke offset on the other. Render both with a changing `value`, then with `value={null}`, to see how the indeterminate state reads before you commit to it in your own UI.

## Anatomy

Progress composes a Progress root, which owns `value` and `max`, around a ProgressIndicator that renders the filled portion; ProgressCircle mirrors that exact shape with ProgressCircle and ProgressCircleIndicator. Both indicator components accept `asChild` if you want to render your own element in the indicator's place instead of the default div.

## State model

There's no internal state here — `value` is a plain number (or `null` for an indeterminate look) that you own and update on every render, and `max` sets whatever total makes sense for your unit. `getValueLabel(value, max)` turns that pair into the text announced to assistive tech, so use it when the raw percentage on its own wouldn't mean anything to a user, like "3 of 5 files uploaded".

## Keyboard and accessibility

Both variants render `role="progressbar"` with `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` kept in sync with `value`/`max`, per PROGRESS_A11Y_CONTRACT. Neither is focusable, so there's no keyboard behavior to test — the accessibility work on your end is almost entirely about supplying a meaningful `getValueLabel`.

## Styling and tokens

`data-state` and `data-percentage` land on both the root and the indicator, which is enough for CSS to drive fill width or `stroke-dashoffset` without reading JavaScript state. ProgressCircle's indicator additionally carries a `data-progress-circle-indicator` marker so its styles don't collide with the linear indicator's selectors when both appear in the same stylesheet.

## API

Progress and ProgressCircle share the same own-props shape: `value?: number | null`, `max?: number`, and `getValueLabel?: (value, max) => string`, layered on top of whatever `div` props you pass through. ProgressIndicator and ProgressCircleIndicator are purely visual and take no props beyond `asChild`.

## Edge cases

`value={null}` is the documented way to signal an indeterminate state, and it should look visually distinct from a real `0` — a user reading "0%" and "unknown yet" as the same thing is a bug, not a styling detail. If you skip `getValueLabel`, the default announcement degrades to a raw percentage, which is fine for a file upload but not for something like disk usage where the number alone doesn't explain what's happening.

## Related pages

See Toast and Sonner for time-boxed feedback instead of a persistent indicator, and Alert, Badge, Empty, Skeleton, and Spinner for the indeterminate-loading counterpart that pairs naturally with Progress.

## Documentation navigation

[Previous](https://askrjs.com/docs/components/accordion-and-collapsible/index.md) | [Next](https://askrjs.com/docs/components/toast-and-sonner/index.md)
