Progress
Progress: anatomy, keyboard behavior, state, and theming in Askr.
Example
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. Named types in the Type column define the accepted values.
ProgressIndicatorAsChildProps
Import from @askrjs/themes/progress.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
ProgressIndicatorProps
Import from @askrjs/themes/progress.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ProgressOwnProps
Import from @askrjs/themes/progress.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
getValueLabel | getValueLabel?: ((value: number | null, max: number) => string) | undefined; | — | — |
id | id?: string | undefined; | — | — |
max | max?: number | undefined; | — | — |
value | value?: number | null | undefined; | — | — |
ProgressProps
Import from @askrjs/themes/progress.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
getValueLabel | getValueLabel?: ((value: number | null, max: number) => string) | undefined; | — | — |
id | id?: string | undefined; | — | — |
max | max?: number | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
value | value?: number | null | undefined; | — | — |
ProgressCircleIndicatorAsChildProps
Import from @askrjs/ui/progress-circle.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
ProgressCircleIndicatorProps
Import from @askrjs/ui/progress-circle.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ProgressCircleOwnProps
Import from @askrjs/ui/progress-circle.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
getValueLabel | getValueLabel?: ((value: number | null, max: number) => string) | undefined; | — | — |
id | id?: string | undefined; | — | — |
max | max?: number | undefined; | — | — |
value | value?: number | null | undefined; | — | — |
ProgressCircleProps
Import from @askrjs/ui/progress-circle.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
getValueLabel | getValueLabel?: ((value: number | null, max: number) => string) | undefined; | — | — |
id | id?: string | undefined; | — | — |
max | max?: number | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
value | 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
Provide an accessible name for each progress indicator. The rendered progressbar exposes aria-valuenow, aria-valuemin and aria-valuemax according to value and max; verify those attributes and the value label for your application.
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 Toaster 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.