Progress Bar
A determinate progress indicator built on the role="progressbar" pattern. Communicates task completion to all users — including those using assistive technology.
The ProgressBar communicates the status of a long-running, measurable process. It implements the WAI-ARIA progressbar pattern with the right ARIA attributes, clamps out-of-range values, and supports four semantic colour variants paired with a visible label.
When to Use
- You can measure progress. File uploads, multi-step forms, batch jobs, scan progress — anywhere you know the current value and total.
- The wait is more than ~1 second. Below that, a brief loading state is enough — a progress bar that flashes by is more distracting than helpful.
- You want to set expectations. A determinate bar tells users not just that something is happening, but how much longer they'll wait.
If progress can't be measured, use a spinner with role="status" and a polite live region instead — see Indeterminate Progress below. For initial page or content load, prefer a Skeleton.
Accessibility Features
- Semantic role: Uses
role="progressbar"witharia-valuenow,aria-valuemin, andaria-valuemax— meets WCAG 4.1.2 (Name, Role, Value). - Required label: The
labelprop is required and surfaces both visually and asaria-label. Satisfies WCAG 1.3.1 (Info and Relationships). - Percentage announcement: Screen readers compute the percentage from valuenow/min/max and announce it on update.
- Custom value text: Use
valueText(mapped toaria-valuetext) when "73%" is less helpful than "Step 3 of 5" or "73 of 100 files uploaded". - Not colour-only: Variants (success/warning/danger) reinforce status, but the visible label always carries the meaning — meets WCAG 1.4.1 (Use of Color).
- Out-of-range safe: Values outside
[min, max]are clamped, so the bar never overflows visually or sends invalid ARIA values.
Usage Examples
Basic upload
import { ProgressBar } from '@holmdigital/components'; <ProgressBar value={65} label="Uploading file..." showValueLabel />Multi-step form (custom range + valueText)
<ProgressBar value={3} min={0} max={5} label="Form progress" valueText="Step 3 of 5: Payment" variant="success" showValueLabel />The visible label says "Form progress" while screen readers announce the more informative "Step 3 of 5: Payment".
Reflecting state with variants
function ScanProgress({ value, status }) { const variant = status === 'failed' ? 'danger' : status === 'warning' ? 'warning' : status === 'done' ? 'success' : 'primary'; return ( <ProgressBar value={value} label={`Accessibility scan: ${status}`} variant={variant} showValueLabel /> ); }Indeterminate Progress
<div role="status" aria-live="polite" className="flex items-center gap-2"> <Spinner aria-hidden="true" /> <span>Loading results…</span> </div>
When the operation finishes, replace the contents of the same role="status" region with the result message — screen readers will announce it politely without stealing focus. This pattern aligns with WCAG 4.1.3 (Status Messages).
Live Updates & Screen Readers
Screen readers announce aria-valuenow changes on a progressbar automatically, but they vary in how often. A few practical guidelines:
- Throttle rapid updates. If your progress updates 30+ times per second, batch them — typical guidance is to update no more than every 250–500 ms.
- Announce milestones, not noise. For long jobs, consider a separate polite live region for "0 % → 25 % → 50 % → 75 % → done" while the bar itself updates smoothly.
- Don't move focus on completion. When the task finishes, update text instead of moving focus — moving focus during background work disrupts what the user is doing.
Props
Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| value * | number | - | Current progress value. Clamped to [min, max]. |
| label * | string | - | Accessible label. Rendered visibly above the bar AND set as aria-label. |
| min | number | 0 | Minimum value (aria-valuemin). |
| max | number | 100 | Maximum value (aria-valuemax). |
| showValueLabel | boolean | false | Renders a visible percentage next to the label (e.g. "65 %"). |
| valueText | string | - | Overrides the screen reader's value announcement via aria-valuetext (e.g. 'Step 3 of 5'). |
| variant | 'primary' | 'success' | 'warning' | 'danger' | 'primary' | Color variant. Pair with a meaningful label — colour alone is not enough (WCAG 1.4.1). |
| className | string | - | Additional CSS classes for the wrapper element. |
Best Practices
- Always provide a meaningful label. "Loading" is not enough — say what is loading. The label is required and is what assistive tech reads first.
- Use
valueTextwhen units matter. "47 of 200 files" or "Step 3 of 5" tells the user something a percentage doesn't. - Match variant to status, not aesthetic.
dangermeans "something went wrong"; reserve it for real failure states. - Show the visible percentage when it adds value. For short tasks (under ~5 s) the bar itself is enough; for long ones,
showValueLabelreassures sighted users that work is still happening. - Pair with a separate error message on failure. A red bar at 60 % does not explain what went wrong — surface the reason via a status message or alert.
Common Mistakes
label, screen reader users hear "progress bar, 65 percent" — 65 % of what?value 60 times per second. The visible animation is smooth, but rapid aria-valuenow changes can spam screen reader announcements. Throttle to ~250 ms.