Who it affects
Someone using a screen reader hears progress bar, 62 percent and does not know if it is their upload, the page loading, or an advert.
Someone using a screen reader while two uploads run hears two unnamed bars and cannot tell which file is nearly done.
Someone using voice control cannot refer to the bar to ask for its value.
Broken versus fixed
Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.
The file name is right above the bar. A screen reader announces: progress bar, 62 percent. The file name is separate text and is not part of the bar's announcement.
Announced: Uploading holiday-photos.zip, progress bar, 62 percent. The visible text is the name, so there is one thing to keep up to date.
Show the markup behind both examples
<style>body{font-family:system-ui;padding:16px;margin:0;font-size:14px}.f{display:flex;justify-content:space-between;width:300px;margin-bottom:6px}.n{color:#2d3748}.p{color:#718096}.bar{width:300px;height:10px;border-radius:5px;background:#e2e8f0;overflow:hidden}.bar span{display:block;height:100%;width:62%;background:#2b6cb0}</style>
<div class="f"><span class="n">holiday-photos.zip</span><span class="p">62%</span></div>
<div class="bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="62"><span></span></div>
<style>body{font-family:system-ui;padding:16px;margin:0;font-size:14px}.f{display:flex;justify-content:space-between;width:300px;margin-bottom:6px}.n{color:#2d3748}.p{color:#718096}.bar{width:300px;height:10px;border-radius:5px;background:#e2e8f0;overflow:hidden}.bar span{display:block;height:100%;width:62%;background:#2b6cb0}</style>
<div class="f"><span class="n" id="up">Uploading holiday-photos.zip</span><span class="p">62%</span></div>
<div class="bar" role="progressbar" aria-labelledby="up" aria-valuemin="0" aria-valuemax="100" aria-valuenow="62"><span></span></div>
Test it by hand in two minutes
- Inspect the element with role="progressbar". It needs aria-label, aria-labelledby pointing at visible text, or to be a native progress element with a label.
- In Chrome DevTools, select it and open the Accessibility pane. The computed Name should say what is progressing.
- Turn on VoiceOver (Cmd+F5) or NVDA and move to the bar. You should hear the name, then progress bar, then the value.
- If the bar is indeterminate (no aria-valuenow), check the name on its own tells the listener what to wait for.
The fix
<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="62"></div>
<div role="progressbar" aria-label="Uploading holiday-photos.zip" aria-valuemin="0" aria-valuemax="100" aria-valuenow="62"></div>
Say what is in progress. If there is text on screen that already says it, use aria-labelledby and point at that instead.
Native progress element
<div role="progressbar" aria-valuenow="62" aria-valuemin="0" aria-valuemax="100"></div>
<label for="up">Uploading holiday-photos.zip</label>
<progress id="up" max="100" value="62">62%</progress>
The native element takes a label like any form control and reports its own value.
Multi-step form
<div role="progressbar" aria-valuenow="2" aria-valuemin="0" aria-valuemax="4"></div>
<div role="progressbar" aria-label="Checkout progress" aria-valuenow="2" aria-valuemin="0" aria-valuemax="4" aria-valuetext="Step 2 of 4: Delivery"></div>
aria-valuetext turns the raw number into something a person would say.
Common mistakes
- Naming it progress bar or loading, which repeats the role and says nothing about what is loading.
- Putting the name on a wrapper rather than on the element with the role.
- Hiding the visible file name with aria-hidden and then referencing it with aria-labelledby, which gives an empty name.
- Forgetting to remove or update the bar when the task finishes, so it announces a stale percentage.
- Using a progressbar for a static measurement like storage used; that is a meter.
WCAG criteria this rule maps to
axe-core tags this rule as WCAG 2.2 level A. It is one of the checks for:
- 1.1.1 Non-text Content (level A). Official W3C text.
The scanner can see a progressbar with an empty computed name. It cannot tell whether the name describes the right task, or whether the value is updated as the task runs. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.
Related rules
aria-meter-name
role="meter" is for a measurement within a known range: password strength, disk space used, battery level.
A
Required ARIA attributes must be provided for the role
aria-required-attr
Some ARIA roles only make sense with a state or value attached.
A
ARIA attributes must have valid values
aria-valid-attr-value
Each ARIA attribute accepts a particular kind of value: true or false, a number, a word from a short list, or the id of another element.
A
Form fields must have labels
label
Every input and textarea needs a label that is programmatically tied to it, not just sitting nearby.