sfw/fix
aria-required-children high

Certain ARIA roles must contain particular children

An ARIA role is missing the child roles it's required to own, breaking the widget for screen readers.

What you see

[aria-required-children] Certain ARIA roles must contain particular children
Element has children which are not allowed, or is missing required children
Failing element: <ul role="tablist"> … (no role="tab" descendants)

What’s actually happening

axe/Lighthouse flag a container whose role demands specific children — role="tablist" with no role="tab", role="listbox" with no role="option", role="menu" with no role="menuitem". To a screen reader the widget announces as a tablist/listbox/menu but exposes nothing inside it, so the user is told there are tabs/options and then finds none. Almost always a hand-built widget rather than a native control.

Common causes

  • A wrapper div with the child role in between, e.g. role="tablist" > <div class="scroll"> > role="tab", which breaks the required parent→child ownership
  • Adding role="tablist"/"menu"/"listbox" to a container but styling the items as plain <li>/<a>/<div> without the matching role on each
  • role="presentation" or role="none" applied to the intermediate <ul>/<li>, stripping the role the parent expected to own
  • An empty state — the list renders the role but the items haven't loaded yet (async data) or were conditionally removed
  • Custom select/combobox built from divs where the popup is role="listbox" but the rows never got role="option"

How to fix it

  1. Add the required child role to each itemFor a tablist, every tab must be role="tab"; for listbox, each option role="option"; for menu/menubar, each item role="menuitem" (or menuitemradio/menuitemcheckbox). Put the role on the element the user actually interacts with, not a wrapper.
  2. Remove wrappers between parent and childThe owned children must be DOM descendants the role can claim. If a scroll/flex div sits between role="tablist" and the tabs, either move the role onto that inner container, or set the wrapper to role="presentation" so it's transparent to the accessibility tree — but only the wrapper, never the tabs themselves.
  3. Use aria-owns when the DOM can't be nestedIf the children genuinely can't be physical descendants (e.g. the listbox popup is portaled to <body>), keep the relationship with aria-owns="id1 id2…" on the parent pointing at the child element ids. That satisfies the ownership requirement without moving the DOM.
  4. Handle the empty/async caseDon't render role="listbox"/"tablist" with zero required children. Either wait to apply the role until items exist, or include the items (even if loading placeholders carry the child role), so the audit never sees an empty required-children container.
  5. Prefer native elements where you canIf the widget is really a list of links or a set of buttons, drop the ARIA roles and use semantic HTML (<nav><ul><li><a>, or <button>s). Native elements come with the right semantics for free and sidestep this whole class of bug. Reach for tab/listbox/menu roles only when you're also implementing the full keyboard interaction pattern.

Stop it recurring

When you apply a composite role (tablist, menu, listbox, grid), add its required child roles in the same component and keep them as direct descendants or wire them with aria-owns.

Related errors