[aria-hidden="true"] elements contain focusable descendants
An element pulled from the accessibility tree with aria-hidden="true" still contains focusable children, creating ghost tab stops.
What you see
ARIA hidden element must not be focusable or contain focusable elements (aria-hidden-focus) Fix any of the following: Focusable descendant of an aria-hidden element <a class="nav-link" href="/pricing">Pricing</a>
What’s actually happening
A keyboard user tabs into an element a screen reader announces as not there. The container has aria-hidden="true", so assistive tech skips it — but its links, buttons, or inputs are still in the tab order. The result is a focus stop on an invisible or "hidden" control: the screen reader goes silent, the focus ring lands on nothing useful, and people get stranded. axe-core's aria-hidden-focus rule flags this and names the focusable descendant. It's extremely common on off-canvas menus, closed modals, and carousels that aria-hide a panel but leave its tabindex intact.
Common causes
- A toggled menu or modal sets aria-hidden="true" when closed but leaves its links/buttons in the natural tab order (no display:none, no inert).
- A carousel or tab widget hides off-screen slides/panels with aria-hidden while their controls remain focusable.
- aria-hidden="true" slapped on a wrapper to "hide" it visually, when the intent was display:none or visibility:hidden.
- A decorative icon wrapper marked aria-hidden that wraps an actual <a> or <button>.
- A framework component that adds aria-hidden for screen readers but never disables tabbing on the children it contains.
How to fix it
- Decide: truly hidden, or just removed from the a11y tree?aria-hidden="true" only removes an element from the accessibility tree — it does nothing to focus or visibility. If the element should be fully gone for everyone, that's the wrong tool. If you genuinely want it visible but unannounced (rare), you must also remove its children from the tab order.
- Hide closed menus/modals with display:none or the hidden attributeFor an off-canvas nav or closed dialog, toggle display:none (or the HTML hidden attribute) instead of aria-hidden. display:none removes elements from both the a11y tree and the tab order in one move, so there are no ghost stops. Drop the aria-hidden once you do this.
- Use inert when you need it in the DOM but non-interactiveFor content that must stay rendered (animating drawer, background behind an open modal), set the inert attribute on the container. inert removes descendants from focus order and the a11y tree without display:none. It's supported in all current evergreen browsers; for older targets use a tabindex/aria-hidden polyfill or the focus-trap approach below.
- If it must stay focusable-by-design, drop the aria-hiddenIf the children genuinely need to be reachable (you were hiding the wrong thing), remove aria-hidden="true" from the ancestor so the tree and the tab order agree. Visible-and-focusable but a11y-hidden is almost never correct.
- Re-scan and tab through itRe-run axe DevTools, then actually Tab through the component with the menu/modal both open and closed. Closed state should have zero tab stops inside the hidden region; open state should announce normally in VoiceOver or NVDA.
Stop it recurring
Toggle visibility with display:none or inert rather than aria-hidden, so an element's accessibility-tree state and its tab order never disagree.