Progress
A horizontal progress bar for measurable, ongoing tasks — determinate or indeterminate, built on Base UI Progress.
- Status
- Since
0.1.0- Accessibility pattern
- ARIA progressbar
Last updated
Install
Add Progress from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/progressUsage
import { Progress } from "@/components/ui/progress";
<Progress value={60} aria-label="Upload progress" />;Examples
Anatomy
Progress exposes a flat API while composing Base UI's Root → Track → Indicator parts internally:
- Root — the
role="progressbar"element (data-slot="progress").classNameapplies here. - Track — the muted rail (
data-slot="progress-track"). UsetrackClassNamefor rail styling. - Indicator — the primary fill (
data-slot="progress-indicator"). UseindicatorClassNamefor fill styling; width and progress semantics are managed by Base UI.
Values
value is reported against max (default 100). The indicator width animates between values with a token-driven transition.
Sizes
sm (6px), md (8px), and lg (12px) — the track and indicator scale together.
Custom scale
Pass max to track progress on an arbitrary scale — e.g. step 3 of 5. The value is reported to assistive tech as value / max (here, 60%):
<Progress value={3} max={5} size="lg" aria-label="Step 3 of 5" />Indeterminate
Pass value={null} when the duration is unknown. Base UI drops aria-valuenow and sets
data-indeterminate; the indicator becomes a 35%-wide segment sweeping the track, not a
filled bar.
That distinction is the whole point: Base UI writes no inline width for an indeterminate
indicator, so a bar styled only for the determinate case inherits the track's full width and
reads as 100% complete — an upload in progress looking finished. Under
prefers-reduced-motion the global reset collapses the sweep to its resting frame, a static
35% segment, so it still never reads as complete.
<Progress value={null} aria-label="Loading" />Colored fill
Recolor the fill with indicatorClassName to signal a status tone — the default is bg-primary. Use a semantic token (bg-success, bg-warning, bg-destructive):
<Progress
value={66}
indicatorClassName="bg-success"
aria-label="Success fill"
/>Custom track
trackClassName styles the rail behind the fill (default bg-muted). Pair it with indicatorClassName to theme both layers — className stays on the accessible progressbar root:
<Progress
value={50}
trackClassName="bg-info-subtle"
indicatorClassName="bg-info"
aria-label="Custom track"
/>Size × value
size and value are independent axes — every height works at every fill level.
Custom root element (render)
Use Base UI's render to swap the root element while keeping the role="progressbar" semantics, the merged data-slot / state data-*, the forwarded ref, and the Track / Indicator children. The replacement element must support progressbar semantics:
<Progress value={60} aria-label="Upload progress" render={<output />} />Playground
Adjust the bar's value and size — the fill sweeps to each new value — then copy the generated JSX.
<Progress value={50} aria-label="Upload progress" />API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
indicatorClassName | string | — | Classes for the fill indicator. Use this to change the fill token or motion treatment without replacing the accessible progress root. |
max | number | 100 | Upper bound of the scale — value is reported as value / max. |
render | ComponentRenderFn<HTMLProps, ProgressRootState> | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | — | Replace the rendered root element via Base UI render composition. Pass a
ReactElement or a render function — Base UI merges this
wrapper's data-slot and state data-* onto your element, forwards the ref,
and keeps the <Progress.Track> / <Progress.Indicator> children. The
element must support role="progressbar" semantics. |
size | ProgressSize | 'md' | Track + indicator height. sm (6px), md (8px), lg (12px). |
trackClassName | string | — | Classes for the inner track rail. Use this for track width/height/color
overrides; className belongs to the root progressbar element. |
value | number | null | Current completion value, between 0 and max. Pass null for an
indeterminate bar (Base UI sets data-indeterminate and drops
aria-valuenow). |
Data attributes and CSS variables on Progress
| Attribute | Values |
|---|---|
data-size | mirrors a prop or state value |
data-slot | "progress" | "progress-indicator" | "progress-track" |
Accessibility
- Renders
role="progressbar"witharia-valuenow/aria-valuemin/aria-valuemaxmanaged by Base UI. - When
valueisnullthe bar is indeterminate —aria-valuenowis omitted anddata-indeterminateis set. - Always give the bar an accessible name — an
aria-label, or associate a visible label viaaria-labelledby. - The fill transition and the indeterminate sweep both stop under
prefers-reduced-motionthrough the global reset inbase.css— no component restates it.
| Attribute | Purpose |
|---|---|
role="progressbar" | Exposes the element as a progress bar to assistive tech |
aria-valuenow | Current value (omitted while indeterminate) |
aria-valuemin / aria-valuemax | Bounds of the scale (0 and max) |
aria-label / aria-labelledby | Accessible name (author-provided) |
| Contract | States tested |
|---|---|
| Behaviour | default, indeterminate |
| Accessibility | labeled |
| Visual | default |