Progress
Use progress with the current published Askr packages.
@askrjs/ui/progress0.0.13@askrjs/ui/progress-circle0.0.13@askrjs/themes/progress0.0.13
How to use progress
Progress should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.
- Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
- Keep progress labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
- Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Progress } from '@askrjs/themes/components';
<Progress value={uploaded()} max={total()} aria-label="Upload progress" />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
Run the linear and circular variants side by side on this page and watch how `value` changes drive the fill width or the ring's stroke offset in real time. That's also the fastest way to see what an indeterminate `value={null}` state actually looks like 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.