Progress
A determinate bar showing how far a known-length task has advanced.
@garn/ui/progressOn this page
Default
A labeled determinate bar at 60%.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add progress- Registry
progress- Deps
@radix-ui/react-progressclass-variance-authority- Registry deps
utils
Import
import { Progress } from "@garn/ui/progress";Props
The component's public props and their types.
format- Description
- Format the read-out + `aria-valuetext`.
- Type
(value: number, max: number) => string- Default
(value, max) => `${Math.round((value / max) * 100)}%`
indeterminate- Description
- Force indeterminate even with no value (the unknown-progress mode).
- Type
boolean- Default
false
label- Description
- A visible caption (rendered in a ProgressLabel; becomes the accessible name).
- Type
React.ReactNode
showValue- Description
- Show the value read-out beside the label.
- Type
boolean- Default
false
size- Description
- Track height.
- Type
"lg" | "md" | "sm"- Default
"md"
thresholds- Description
- Ascending value→tone map for `tone="auto"`.
- Type
ProgressThreshold[]- Default
[{ at: 0, tone: "success" }, { at: 75, tone: "warning" }, { at: 90, tone: "danger" }]
tone- Description
- `neutral | brand | success | warning | danger | info`, or `"auto"` to drive the tone from the value via `thresholds`.
- Type
"auto" | ProgressTone- Default
"brand"
value- Description
- 0–`max`. `null`/`undefined` → indeterminate (or set `indeterminate`).
- Type
number
Plus 282 inherited native <div> attributes.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="progress"]States
| Selector | State | Description |
|---|---|---|
[data-state=indeterminate] | indeterminate | No `value` passed — progress is unknown. |
[data-state=loading] | loading | 0 ≤ value < max. |
[data-state=complete] | complete | value === max. |
When to use
Show determinate progress of a task with a known percentage — upload, install, multi-step completion.
Reach for something else when
- An indeterminate wait with no measurable percentage (use a spinner)
- a static ratio or score (use plain text or a meter).
Overview
Progress shows the determinate progress of a task with a known percentage — an upload, an install, multi-step completion — as a filled bar with a tone that can shift at thresholds, and it doubles as a capacity meter (storage used, quota). Use it when you can report a real fraction; for an indeterminate wait use a Spinner, and to hold a content shape while loading use a Skeleton.
Guidelines
Use Progress only when you know the fraction. A determinate bar that actually advances reassures users; faking it (or snapping 0→100) erodes trust. For unknown-duration work, a Spinner is the honest choice.
Pair the bar with a real number and context. Show the percentage or "3 of 5", and label what's progressing — a bar alone leaves users guessing. As a capacity meter, make the tone shift meaningful (e.g. warning as a quota fills).
Wire it for assistive tech. Expose the value via the progress role (aria-valuenow/min/max) and an accessible name so the state is announced, not just shown. Keep updates smooth and avoid jarring jumps; respect reduced-motion on any animation.
Best practices
- Pass
valueas 0–100 and give the bar an accessible name (aria-label or aria-labelledby). - Pair it with a visible percentage or step label when the number matters.
- Don't use Progress for unknown-duration loading — that's a Spinner.
- Don't animate a fake value just to look busy.
Content guidelines
- Give it an accessible label naming the task (“Uploading photo”).
- When you show a percentage or count, keep it true to the real value.
Troubleshooting
The bar renders but is announced with no name.
Cause. Progress has no text content of its own.
Fix. Pass aria-label (or aria-labelledby pointing at a visible label).
Accessibility
- Role
progressbar
- Radix sets aria-valuenow / aria-valuemin / aria-valuemax from `value` and `max`.
- The bar carries no text — give it an accessible name via aria-label or aria-labelledby.