sfw/fix
aria-hidden-focus high

ARIA hidden element must not be focusable or contain focusables

An element with aria-hidden="true" is itself focusable or wraps a focusable control, creating a tab stop that screen readers cannot announce.

What you see

Certain ARIA roles must contain particular children
aria-hidden="true" elements must not be focusable or contain focusable elements
Element: <button aria-hidden="true">Close</button>

What’s actually happening

A keyboard user presses Tab and focus lands somewhere, but the screen reader says nothing — no name, no role, dead air. The control is still clickable and operable by keyboard, yet it's missing from the accessibility tree because something set aria-hidden="true" on it or on an ancestor. Sighted keyboard users see the focus ring move to an invisible-to-AT element; screen reader users get a phantom stop they can't identify. Activating it can fire real behavior (submit, navigate, close a dialog) with zero feedback.

Common causes

  • aria-hidden="true" was placed directly on a <button>, <a href>, <input>, or other natively focusable element to "hide" it visually instead of using CSS
  • A wrapper got aria-hidden="true" (a decorative carousel slide, an off-canvas menu, a modal's background) while focusable children inside it kept their normal tab order
  • An off-screen control is positioned with left:-9999px or clip but never had its focus removed, then aria-hidden was added on top
  • A component library hides inactive tab panels or collapsed accordions with aria-hidden but leaves the buttons/links inside reachable by Tab
  • aria-disabled="true" was used in place of the real disabled attribute, so the field stays focusable while marked hidden/inactive

How to fix it

  1. Use inert on the whole hidden subtree instead of aria-hidden aloneFor backgrounds behind modals, off-canvas drawers, and hidden slides, set the inert attribute on the container. inert removes the element and everything inside it from the tab order and the accessibility tree in one move, so you don't have to hunt down every focusable child. Supported in all current browsers; polyfill older ones if you still support them.
  2. Hide it with display:none or the hidden attribute when it should be fully goneIf the content is genuinely not available right now (collapsed accordion body, inactive tab panel), display:none or the HTML hidden attribute removes it from layout, the focus order, and the a11y tree. This is almost always the right tool for show/hide and beats aria-hidden + manual focus management.
  3. If the element must stay visible, remove focusability with tabindex="-1"When the control has to render but not take Tab focus, add tabindex="-1" to it and every focusable descendant. Note the gap: tabindex="-1" keeps it scriptable via .focus(), so make sure your JS never programmatically focuses an aria-hidden node — that reintroduces the silent stop.
  4. Strip aria-hidden off interactive controls entirelyNever put aria-hidden="true" on a <button>, <a>, or form field you intend to keep working. aria-hidden is for decorative or duplicated content (icon glyphs, presentational SVGs). If a control should be exposed, drop the attribute; if it shouldn't exist, remove or disable it.
  5. Verify the fix with keyboard + screen reader, not just the scannerTab through the region with VoiceOver (Safari) or NVDA (Firefox/Chrome). Every focus ring should land on something that gets announced. If focus stops on silence, you still have a focusable node inside an aria-hidden subtree.

Stop it recurring

Reach for inert or display:none to hide regions, and reserve aria-hidden="true" for purely decorative, non-focusable content.

Related errors