How to Fix ARIA Required Children and Parent Relationships? Complete Guide (2026)

Saar Twito9 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Are ARIA Required Children and Parent Relationships?

Some ARIA roles describe composite widgets — controls built from multiple coordinated parts. A role="tablist" is meaningless without role="tab" children. A role="listitem" is meaningless outside a role="list". Two audits enforce these relationships: aria-required-children fails when a parent role is missing the child roles it requires, and aria-required-parent fails when a child role appears outside its required parent.

Key Facts (TL;DR)

  • WCAG criteria: Success Criterion 1.3.1 Info and Relationships (Level A) and 4.1.2 Name, Role, Value (Level A). Broken role pairings break both.
  • Two related audits: aria-required-children (parent missing required child role) and aria-required-parent (child role outside its required parent).
  • Most-affected widgets: lists, tab strips, menus, listboxes/comboboxes, radiogroups, trees, and grids — all composite widgets defined by their parts.
  • Why it's common: WebAIM's 2025 Million homepage analysis found pages using ARIA averaged more detected accessibility errors than pages without it — composite-widget structure mistakes are a major contributor.
  • Native HTML alternative: <ul>/<li>, <ol>/<li>, <select>/<option>, and <dl>/<dt>/<dd> handle the parent/child contract automatically.

Think of composite widgets the way you think of a chess set: a board without pieces is just a checkered surface, and a knight off the board is just a horse-shaped object. Each only makes sense in relation to the other.

Why Required Parent/Child Pairings Matter

Composite-widget roles do not stand alone. Screen readers rely on the parent/child structure to compute the "1 of 5" counters, the "tab 2 of 4 selected" announcements, and the keyboard navigation patterns (arrow keys inside a tablist, Home and End inside a listbox). When the structure is broken, every one of those features breaks.

  • Counters disappear: A role="list" with no listitemchildren is announced as "list, empty" even when items are visible. The user cannot tell how many items are present.
  • Keyboard patterns break: A role="tab" outside a role="tablist" does not get arrow-key navigation. Keyboard users are stuck.
  • Selection state is lost: role="option" outside a role="listbox" can announce aria-selected, but the user has no listbox to navigate, so the state is meaningless.
  • Trees collapse to flat lists: A role="treeitem" outside a role="tree" loses the hierarchy. Expand/collapse no longer makes sense.
  • WCAG 1.3.1 violations: Info and Relationships requires that programmatic structure matches visual structure. A visually nested tree that programmatically is a flat row of items fails 1.3.1.
  • AI search visibility:AI answer engines lean on semantic HTML and the accessibility tree to understand a page's structure. Broken composite roles produce a noisier tree, weaker structured signals, and weaker citation candidates for Google AI Overviews and Perplexity.

The Required Parent/Child Pairings You Must Know

The WAI-ARIA specification defines the full list. The pairings most likely to fail an audit:

Parent roleRequired child role(s)Native HTML equivalent
listlistitem<ul> / <ol> with <li>
tablisttab(no native equivalent)
menu / menubarmenuitem / menuitemcheckbox / menuitemradio(no native equivalent)
listboxoption (or grouped via group)<select> / <option>
radiogroupradio<input type="radio"> with shared name
treetreeitem (often grouped via group)(no native equivalent)
grid / treegrid / tablerow with gridcell / columnheader / rowheader<table> with <tr> and <td> / <th>
combobox (with popup)Popup listbox (or tree, grid, dialog)<input> + <datalist>

The relationship runs both ways. aria-required-children fails when a parent has no children of the required role; aria-required-parent fails when a child appears without the required ancestor.

How to Fix aria-required-children

List with no listitems

The role="list" wrapper has visible bullet items, but each item is a <div>with no role. The screen reader announces "list, empty."

<!-- BAD -->
<div role="list">
  <div>Apples</div>
  <div>Bananas</div>
  <div>Cherries</div>
</div>

<!-- GOOD: explicit listitem children -->
<div role="list">
  <div role="listitem">Apples</div>
  <div role="listitem">Bananas</div>
  <div role="listitem">Cherries</div>
</div>

<!-- BEST: native semantics -->
<ul>
  <li>Apples</li>
  <li>Bananas</li>
  <li>Cherries</li>
</ul>

Tablist with no tabs

A horizontal strip of buttons styled as tabs but built without role="tab". Arrow-key navigation is broken and the strip is announced as a generic group.

<!-- BAD -->
<div role="tablist">
  <button>Overview</button>
  <button>Pricing</button>
  <button>FAQ</button>
</div>

<!-- GOOD -->
<div role="tablist" aria-label="Plan details">
  <button role="tab" aria-selected="true" aria-controls="p1" id="t1">Overview</button>
  <button role="tab" aria-selected="false" aria-controls="p2" id="t2">Pricing</button>
  <button role="tab" aria-selected="false" aria-controls="p3" id="t3">FAQ</button>
</div>

Menu with no menuitems

A dropdown built with role="menu" that contains plain links. Screen readers announce the popup as a menu but find nothing inside it to navigate.

<!-- BAD -->
<ul role="menu">
  <li><a href="/edit">Edit</a></li>
  <li><a href="/delete">Delete</a></li>
</ul>

<!-- GOOD -->
<ul role="menu">
  <li role="menuitem">Edit</li>
  <li role="menuitem">Delete</li>
</ul>

If the items are real navigation links rather than commands, use a <nav> with a list — role="menu" is for application-style menus only.

Listbox with no options

Custom select widgets sometimes wrap a list of <div> rows in role="listbox" without giving each row role="option". Selection state and arrow-key navigation both break.

<!-- BAD -->
<ul role="listbox">
  <li>Small</li>
  <li>Medium</li>
  <li>Large</li>
</ul>

<!-- GOOD -->
<ul role="listbox" aria-label="Size">
  <li role="option" aria-selected="false">Small</li>
  <li role="option" aria-selected="true">Medium</li>
  <li role="option" aria-selected="false">Large</li>
</ul>

How to Fix aria-required-parent

listitem outside a list

A row in a card grid is given role="listitem" with no role="list" wrapper. The role becomes orphaned and is dropped from the accessibility tree.

<!-- BAD -->
<div className="grid">
  <article role="listitem">Card 1</article>
  <article role="listitem">Card 2</article>
</div>

<!-- GOOD -->
<div role="list" className="grid">
  <article role="listitem">Card 1</article>
  <article role="listitem">Card 2</article>
</div>

tab outside a tablist

role="tab" requires role="tablist"as a direct ancestor. A single "tab" on its own makes no sense — there is nothing to switch between.

<!-- BAD -->
<button role="tab">Settings</button>

<!-- GOOD -->
<div role="tablist" aria-label="Account">
  <button role="tab" aria-selected="true">Settings</button>
  <button role="tab" aria-selected="false">Billing</button>
</div>

option outside a listbox

A search-result row with role="option" sitting in a plain <div>. The option role implies single or multi-select — without a listbox parent, no selection model exists.

<!-- BAD -->
<div className="results">
  <div role="option">First match</div>
  <div role="option">Second match</div>
</div>

<!-- GOOD -->
<div role="listbox" aria-label="Search results">
  <div role="option">First match</div>
  <div role="option">Second match</div>
</div>

treeitem outside a tree

File-row components are sometimes reused outside a tree (in a flat list, in a search-results panel) but keep role="treeitem". Expand/collapse semantics no longer apply.

Either wrap them in a role="tree" with the appropriate group sub-roles, or change the role on the row when it is rendered outside the tree. A reusable component should accept the role as a prop, not hardcode it.

How to Check Your Pages

  • Greadme deep scan — runs both aria-required-children and aria-required-parent, lists every parent missing required children and every orphan child role, and offers a one-click GitHub PR fix where applicable.
  • Greadme crawler scan — finds template-level role mistakes that repeat on every page (a single broken tablist component fails on every page that mounts it).
  • Greadme AI visibility analyzer — surfaces how clean composite-widget structure affects how AI answer engines interpret the interactive structure of your pages.
  • Chrome DevTools → Accessibility tree — inspect the tree for any composite role: if the children are not the roles you expect, the audit will fail.
  • axe-core (the open-source rules engine) — integrates into Jest, Playwright, and Cypress for build-time validation of role hierarchies.
  • Manual screen-reader test — open the page in NVDA, VoiceOver, or TalkBack and listen for the "X of N" announcement on lists, tabs, options, and treeitems. If the count is missing, the structure is broken.

10 Rules for Reliable Composite Widgets

1. Prefer native HTML for the simple cases

<ul>, <ol>, <select>, <dl>, and <table> all wire up parent/child semantics for free. The First Rule of ARIA: if a native element exists, use it.

2. If the parent has a role, every visible child needs the matching role

A role="list" wrapper means every visible row inside has to be a listitem. Half the rows with a role and half without is the most common aria-required-children failure.

3. Use role="group" when nesting is required

Trees and listboxes that need sub-groups must wrap the nested set in role="group", not in another tree or listbox. A nested tree without group wrappers fails the parent audit.

4. Don't add interactive children inside a parent role that does not accept them

role="list" accepts only listitem children. Putting a <button> as a direct child can throw both audits. Wrap the button in a listitem.

5. Match the role to the actual interaction pattern

If your tabs do not switch panels with arrow keys, they are not ARIA tabs — they are buttons. Drop role="tablist" rather than fight the audit.

6. Keep the parent and child rendered together

If items are loaded async and the parent renders before the children arrive, aria-required-children may flag the empty state. Either render a loading listitem or do not apply the parent role until the data arrives.

7. Check the DOM hierarchy, not just the rendered tree

aria-required-parent looks at DOM ancestry. A role="treeitem" portaled out of its tree wrapper still fails. Keep composite widgets in one DOM subtree.

8. Use unique IDs for aria-controls wiring

Tab/panel and combobox/listbox pairs use aria-controls and aria-activedescendant to connect parts. React's useId() or any component-scoped ID hook prevents collisions across instances.

9. Lint at build time

eslint-plugin-jsx-a11y rules including jsx-a11y/role-has-required-aria-props, jsx-a11y/role-supports-aria-props, and jsx-a11y/no-noninteractive-element-to-interactive-role catch many composite-widget mistakes before merge.

10. Test with a real screen reader

Listen for the "X of N" announcement when you arrow through items. Working composite widgets always announce position and count; broken ones go silent.

Common Mistakes and How to Avoid Them

Problem: Wrapping list items in extra divs

What's happening: A <ul> contains an animation wrapper <div> that contains the <li>. The browser's implicit list/listitem relationship is broken because <li> is no longer a direct child of <ul>.

Fix: Move the animation wrapper inside the <li>, or use role="list" on the wrapper and role="listitem" on the items so the relationship is explicit regardless of nesting.

Problem: Component reused with the wrong role

What's happening: A FileRow component hardcodes role="treeitem". When the same component is used in a flat search-results list, it fails aria-required-parent.

Fix: Accept the role as a prop. Pass role="treeitem" from the tree and role="option" (with a listbox wrapper) from the search results.

Problem: CSS Grid breaks the role hierarchy

What's happening: CSS display: contents on the parent flattens the layout, but the developer also changes the markup so the children are no longer DOM descendants of the role parent.

Fix: CSS layout does not affect the DOM tree, but moving elements with JavaScript or portals does. Keep the composite widget's parent and children in the same DOM subtree.

Problem: Empty list during loading state

What's happening: role="list" renders before data arrives, fails aria-required-children, and the audit catches the empty state.

Fix: Render a placeholder role="listitem" with a loading message, or hold off on applying the parent role until at least one item exists.

FAQ

What does the aria-required-children audit check?

It verifies that every element with a composite-widget role contains the child roles its parent role requires. A role="list" with no listitem children fails. A role="tablist" with no tab children fails.

What does the aria-required-parent audit check?

It verifies that every child role appears inside its required parent role. A role="listitem" outside a role="list" fails. A role="tab" outside a role="tablist" fails.

Does my <ul>/<li> markup pass these audits automatically?

Yes — native HTML provides the implicit roles. <ul> implies role="list" and <li> implies role="listitem" as long as <li> is a direct child of <ul>. Some browsers strip the implicit list role when the list is styled with list-style: none; if that affects your screen-reader testing, add an explicit role="list" to the <ul>.

Is broken composite structure a WCAG failure?

Yes. It maps to Success Criterion 1.3.1 Info and Relationships (Level A) when the visual structure does not match the programmatic structure, and to 4.1.2 Name, Role, Value (Level A) when role announcements break.

Can I use a div as a listitem if I add role="listitem"?

Yes — that is exactly what role="listitem" exists for. But the parent must also have role="list" (or be a native <ul> / <ol>), or the orphan-child audit will fail.

Why do my tabs fail even though they look correct?

The most common cause is the role being applied at the wrong level — role="tablist" on the outer container but the tabs nested inside an extra wrapping <div> that interrupts the parent/child relationship. Move the role to the direct parent of the tabs, or apply role="presentation" to the wrapper so the structure flattens.

Do these audits affect AI search engines?

Indirectly. AI answer engines such as Google AI Overviews, ChatGPT, and Perplexity parse the accessibility tree and semantic HTML to understand interactive structure. Broken composite widgets produce a noisier tree, weaker structured signals, and worse rankings in traditional Google search — and AI answer engines preferentially cite pages that already rank well.

Conclusion

aria-required-children and aria-required-parentare the two halves of the same contract: composite widgets only work when the parent role and child roles are paired. The fix is rarely "add more ARIA" — it is usually "use the native HTML element" or "apply the missing half of the pair so the contract is satisfied." Once the role hierarchy matches the visual hierarchy, every assistive-tech feature that depends on it (counters, arrow-key navigation, selection state) starts working again.

Run a Greadme deep scan to see exactly which composite widgets on your site are missing required children and which child roles are orphaned — then fix the component once and clear every instance at the same time.