Who it affects
Someone using a screen reader opens a delivery chooser, hears list box, and then either silence or a run of text with no position, no count and no way to pick one.
Someone using a screen reader's keyboard shortcuts cannot arrow between the choices, because the screen reader has no options to move between.
Someone using voice control says the option they want and nothing is selected, because the words are not attached to a selectable item.
Broken versus fixed
Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.
Three options, one highlighted. A screen reader announces: Delivery option, list box, and then reports no items, or reads the text as one lump. There is nothing to select.
Announced: Delivery option, list box, Standard (3 to 5 days), selected, 1 of 3. Arrow key handling still needs script; the demo only fixes the structure.
Show the markup behind both examples
<style>body{font-family:system-ui;padding:16px;margin:0}.lbl{font-size:14px;color:#4a5568;margin-bottom:6px}.lb{border:1px solid #cbd5e0;border-radius:6px;width:260px}.lb div{padding:8px 12px}.lb .on{background:#ebf4ff;font-weight:600}</style>
<div class="lbl" id="del">Delivery option</div>
<div class="lb" role="listbox" aria-labelledby="del" tabindex="0">
<div class="on">Standard (3 to 5 days)</div>
<div>Next day</div>
<div>Click and collect</div>
</div>
<style>body{font-family:system-ui;padding:16px;margin:0}.lbl{font-size:14px;color:#4a5568;margin-bottom:6px}.lb{border:1px solid #cbd5e0;border-radius:6px;width:260px}.lb div{padding:8px 12px}.lb .on{background:#ebf4ff;font-weight:600}</style>
<div class="lbl" id="del">Delivery option</div>
<div class="lb" role="listbox" aria-labelledby="del" tabindex="0">
<div class="on" role="option" aria-selected="true">Standard (3 to 5 days)</div>
<div role="option" aria-selected="false">Next day</div>
<div role="option" aria-selected="false">Click and collect</div>
</div>
Test it by hand in two minutes
- Inspect the container with the role and look at its direct children. Each one should carry the matching child role (option, tab, menuitem, treeitem, row, listitem).
- In Chrome DevTools, select the container, open the Accessibility pane and expand the tree. The children should appear with their roles, not as generic.
- Turn on VoiceOver (Cmd+F5) or NVDA, move to the container and listen for the item count. Zero items on a visibly populated control is the failure.
- Press the Down arrow and check the screen reader moves to the next item and announces its position.
The fix
<div role="listbox" aria-label="Delivery option">
<div>Standard</div>
<div>Next day</div>
</div>
<div role="listbox" aria-label="Delivery option">
<div role="option" aria-selected="true">Standard</div>
<div role="option" aria-selected="false">Next day</div>
</div>
Put the matching child role on every item. Wrapper elements between the container and the items are tolerated by most screen readers if they have role="presentation", but the cleanest answer is no wrappers at all.
A list built from divs
<div role="list">
<div>Flour</div>
<div>Salt</div>
</div>
<ul>
<li>Flour</li>
<li>Salt</li>
</ul>
role="list" needs role="listitem" children. A real ul and li already have both, and need no ARIA.
A tablist whose tabs are buttons with no role
<div role="tablist">
<button>Details</button>
<button>Reviews</button>
</div>
<div role="tablist">
<button role="tab" aria-selected="true">Details</button>
<button role="tab" aria-selected="false">Reviews</button>
</div>
Buttons are a fine base for tabs, but they need role="tab" so the tablist has tabs in it.
Common mistakes
- Putting the child role on a wrapper div and leaving the real item inside it with no role.
- Adding role="option" to the container as well as the items.
- Removing the listbox role to clear the finding, which leaves an unnamed group of text that cannot be selected from.
- Fixing the roles but forgetting aria-selected, so every option is announced without state.
- Using role="group" between the container and the items when the role does not allow it.
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.3.1 Info and Relationships (level A). Official W3C text.
The scanner knows which child roles each container role requires and can see when none are present, or when something else sits in the way. It cannot tell whether the keyboard behaviour that should go with the roles exists. 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-required-parent
Some roles only make sense inside a particular parent.
A
ARIA roles must be valid
aria-roles
The role attribute only accepts names from the ARIA list: dialog, button, tab, listbox and so on.
A
Lists must only contain list items
list
A ul or ol tells a screen reader 'list, N items' and lets the person jump item by item.
A
Tabs must have an accessible name
aria-tab-name
Every element with role="tab" needs a name: the words a screen reader speaks when the tab gets focus and the words a voice control user says to pick it.