use UI

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 MB

Ready 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.