Progress
A progress bar shows how far through a task someone is when the total is known. Upload at 40%, install at step 3 of 5: the number is the point.
<progress value="40" max="100">Also called Progress bar, Progress indicator, Loading bar.
Click Start upload to run the bar. The fill moves because the task has a known end.
For designers
A progress bar answers one question: how far through is this? A file upload at 67%, step 3 of 5 in onboarding, a video buffer creeping toward the end. The total is known, and the fill should move as the number changes.
The cost is honesty. A bar that jumps backward, stalls at 90% while work continues behind the scenes, or pretends to know a total when it does not, trains people to distrust every bar in the product. Google Drive and Dropbox both show upload percentage for this reason: the number is information people already have on the server side, and hiding it wastes trust.
Pair the bar with a label naming the task. Uploading report.pdf tells someone which of three parallel uploads stalled when they glance back at the tab.
When to use progress
Use progress when
- The total work is known and the current value can be calculated
- Someone might leave the tab open and come back to check how far along things are
- The wait runs longer than a few seconds and a percentage would reduce anxiety
Reach for something else when
- The total is unknown and no honest percentage exists yet. Use loading and skeleton instead.
- The wait is under a second and showing a bar would only flash and disappear. Use loading and skeleton instead.
- The result came back empty. That is the outcome, not a wait. Use an empty state instead.
- The condition is ongoing rather than a one-time task, like an account under review. Use a banner instead.
Variants
With label
Step count
Step 3 of 5
Thin inline
Determinate fill with a label, a step count for multi-part work, and a thin bar for background tasks.
Determinate. A known total with a fill that tracks the current value. Uploads, installs, multi-step forms.
With label. Percentage or step count beside the track: 3 of 5 or 67%. Use when someone might leave the tab open and come back.
Thin inline. A two-pixel bar under a card or across the top of a page, for work that should not demand attention.
How to build a progress bar
The native element is the starting point. Style the track and the fill with CSS.
<label for="upload-progress">Uploading report.pdf</label>
<progress id="upload-progress" value="40" max="100">40%</progress>
Expose the value to assistive tech. On a custom bar, put role="progressbar" on the track, aria-valuenow, aria-valuemin, and aria-valuemax on it, and keep a visually hidden text node updated as the number moves. WCAG 1.4.11 requires the fill to contrast with the track by at least 3:1, which is why most products use a saturated fill on a muted track rather than two shades of grey.
When the total is unknown, use aria-valuetext="Indeterminate" or a spinner instead. A bar with no max is a loading state, not progress. The stepper handles multi-step flows where each step is a named screen rather than a continuous percentage.
Edge cases
Completion. When value hits max, swap the bar for a checkmark or success message within a second. A full bar that sits there forever reads as stuck.
Reduced motion. Animate the fill width with a CSS transition, and snap under prefers-reduced-motion: reduce.
Very long tasks. Past a minute, show estimated time remaining if you have a reliable estimate. A bar alone gives no clue whether forty percent means ten seconds or ten minutes.
Common questions
- Progress bar or spinner?
- Ask whether you know the total. A file upload at 67% has a real number to show. A button mid-submit with no idea how long the server will take gets a spinner. Forcing a bar to move when you do not know the end trains people to distrust every bar in the product.
- Should the bar show a percentage?
- When someone might leave the tab and come back, yes. Step 3 of 5 or 67% gives them something to read at a glance. For a two-second wait, the fill alone is enough.
- What about indeterminate progress?
- Use aria-valuetext="Indeterminate" or a spinner. A bar with no max is a loading state, not progress. macOS installer bars that crawl for minutes without a number are a common source of distrust.
- What happens when the task finishes?
- Swap the bar for a checkmark or success message within a second. A full bar that sits there forever reads as stuck. Pair it with a toast if the person navigated away before it completed.
- How do I expose progress to screen readers?
- On a custom bar, put role="progressbar" on the track with aria-valuenow, aria-valuemin, and aria-valuemax. Update aria-valuenow as the number moves. The native progress element does this for free.
Last reviewed 27 AUG 2026