How to fix an ARIA role that is missing its required parent?
aria-required-parent page
The issue
A screen reader announces “tab” or “list item” but can’t say “2 of 5”, because the item isn’t inside the container that gives it that context. The user hears pieces of a widget without the widget, and the keyboard behavior they expect from it isn’t there.
The cause is an item role with no matching container above it. A role="tab" belongs inside a role="tablist"; an <li> belongs inside a <ul> or <ol>. It often happens when a component renders its items into a different part of the page from its container, or when the container’s role is left off. WCAG 1.3.1 requires structure shown visually to be available in the markup.
Scope
Every visible element whose role requires a parent is checked, whether the role is written in a role attribute or comes from the element itself. Probeo walks up through every ancestor, not only the direct parent, looking for a required role.
| Role | Needs an ancestor with |
|---|---|
listitem (including <li>) |
list or group |
option |
listbox or group |
tab |
tablist |
treeitem |
tree or group |
menuitem, menuitemcheckbox, menuitemradio |
menu, menubar or group |
row (including <tr>) |
table, grid, treegrid or rowgroup |
cell, gridcell, columnheader, rowheader (including <td>) |
row |
caption |
figure, grid, listbox, table, tablist, tree or treegrid |
How to check you have it
- In probeo: the page reports
aria-required-parent, “Certain ARIA roles must be contained by particular parents”, with critical impact, naming the role and the parent roles it needs. - By hand: for each item role, inspect its ancestors in DevTools and confirm one of them carries the required container role.
- With a tool: axe DevTools reports the same rule id. The accessibility pane in Chrome or Firefox DevTools shows each element’s ancestors with their roles.
The fix
Put each item inside the container its role requires, or remove the item role.
For lists and tables, use the native containers: <li> inside <ul> or <ol>, <tr> inside <table>. A role="option" belongs inside a role="listbox". For custom widgets, put the container role on the element that wraps the items.
<!-- Before: tabs with no tab list -->
<div class="tabs">
<button role="tab" aria-selected="true">Monthly</button>
</div>
<!-- After -->
<div class="tabs" role="tablist" aria-label="Billing period">
<button role="tab" aria-selected="true">Monthly</button>
</div>Done when: every item role has an ancestor with a role it requires, and the check above comes back clean.
Do not
- Do not use
<li>for content that isn’t in a list; use a<div>or<p>. - Do not give a container
role="presentation"orrole="none"when its items still carry item roles. - Do not add item roles to elements just for styling hooks.
FAQ
Why is an <li> inside a <div> reported?
An <li> is a list item, and a list item needs a list around it. Inside a <div>, it has none, so it can’t be announced as part of a list. Wrap the items in a <ul> or <ol>, or change them to plain elements.
Does the parent have to be the direct parent?
Not for this check. Probeo accepts the required role on any ancestor. Assistive technology is most reliable when the elements in between have no role of their own.
Is this the same as missing child roles?
They are two sides of the same structure. Missing child roles is a container with no items; this is an item with no container. Fixing one often fixes the other.
Which product detects this
Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.