Progress Bar
Also called progress indicator, loading bar, determinate loader
An indicator showing how much of a known-length operation is complete.
Example
quarterly-report.pdf
4.2 MBReady to upload.
What the indicator exposes
no progressbar in the DOM — the empty track above is decoration
One operation, two indicators. The upload can be measured in bytes, so it gets a bar with a real value. The server-side processing cannot be measured at all — so the bar drops its value and only reports that work is happening, which is exactly what a spinner does.
When to use it
- You can measure completion — bytes uploaded, files processed, steps finished out of a known total.
- The wait is long enough that “is this stuck?” becomes a real question, roughly past a couple of seconds.
- The user has to decide whether to wait or leave, and the remaining time is what they are deciding on.
When not to
- You cannot measure it. An indeterminate bar and a spinner say the same thing, and the spinner is not pretending to be a measurement.
- The wait is under a second. The bar appears and vanishes, which reads as a flash of broken layout.
- You would have to fake it. A bar that crawls to 90% and waits there teaches people to distrust every bar you draw.
Trade-offs
- Turns an open-ended wait into a bounded one, which is the difference between waiting and abandoning.
- Makes stalls visible: a bar that stops moving is information, where a spinner that keeps spinning is not.
- Scales to a total — “4 of 12 files” carries meaning a percentage alone does not.
- It has to be honest, and honest progress is often not smooth — real uploads jump, pause and jump again.
- Percentage of bytes is not percentage of time. The last 5% frequently takes the longest, and users read the bar as a clock.
- Needs enough width to be readable, which makes it awkward inside buttons and table cells.
Accessibility
What this pattern needs in order to work for everyone.
- Keyboard
- Nothing to operate — a progress bar is output, not a control, and must not be in the tab order.
- If the operation can be cancelled, the Cancel button is the focusable thing, not the bar.
- Roles and state
- `role="progressbar"` with `aria-valuenow`, `aria-valuemin` and `aria-valuemax`. Give it a name with `aria-label` or `aria-labelledby` — “progress bar” alone says nothing about what is progressing.
- When it is indeterminate, **remove** `aria-valuenow` entirely. Leaving it at 0 announces “0 percent” forever, which is worse than saying nothing.
- Use `aria-valuetext` when the raw number is not the useful reading — “4 of 12 files” instead of “33”.
- Do not put the bar itself in a live region. Announce the milestones (started, finished, failed) in a polite live region and let the bar be silent in between.
- Focus
- Focus never moves to a progress bar. When the operation finishes, leave focus where the user put it unless the result replaced the thing they were focused on.
In the wild
- Vercel deployments — Named build steps rather than a single percentage, because the steps are what is actually measurable.
- macOS file copy — Shows the bar plus items remaining and an estimate, and switches wording when the estimate becomes unreliable.
Compared with
Side-by-side breakdowns of patterns that solve a similar problem.