How to fix a hidden element that can still take keyboard focus?

aria-hidden-focus page

The issue

A keyboard user tabs onto a control and hears nothing, or hears only “blank”. The link or button is still in the tab order, but aria-hidden="true" has removed it from what a screen reader announces. The user can activate something without knowing what it is.

The cause is a mismatch between two systems. aria-hidden hides an element from assistive technology but does nothing to keyboard focus, so anything focusable inside it stays reachable. It usually comes from a closed menu, a carousel slide or a modal that is hidden with aria-hidden but still sits in the page. WCAG 4.1.2 requires every control’s name and role to be available to assistive technology.

Scope

Every element with aria-hidden="true" is checked, whether or not it is otherwise hidden. It fails if the element itself is focusable or if anything inside it is. Probeo counts as focusable a link with an href, a <button>, <input>, <select>, <textarea> or <summary>, and any element with a tabindex, unless it is disabled or has tabindex="-1".

How to check you have it

  • In probeo: the page reports aria-hidden-focus, “ARIA hidden element must not be focusable or contain focusable elements”, with serious impact, on each aria-hidden element that is focusable or contains a focusable element.
  • By hand: search the page source for aria-hidden="true" and look inside each match for links, buttons, form fields and tabindex. Then tab through the page with a screen reader on and listen for stops that announce nothing.
  • With a tool: axe DevTools reports the same rule id. The accessibility pane in Chrome or Firefox DevTools shows whether a focused element is in the accessibility tree.

The fix

Take everything inside a hidden region out of the tab order, or stop hiding the region from assistive technology.

If the content should be hidden from everyone, as a closed menu is, hide it fully with the hidden attribute or display: none, which removes it from both the accessibility tree and the tab order. If it must stay rendered but unreachable, give each focusable element inside it tabindex="-1", or disabled for form controls, and restore them when the region is shown. If the content is meant to be used, remove aria-hidden.

<!-- Closed menu: hidden from everyone -->
<ul id="menu" hidden>
  <li><a href="/pricing/">Pricing</a></li>
</ul>

Done when: no element with aria-hidden="true" is focusable or contains a focusable element, and the check above comes back clean.

Do not

  • Do not put aria-hidden="true" on content a keyboard user can reach.
  • Do not hide a decorative icon inside a button by putting aria-hidden on the button itself; put it on the icon.
  • Do not rely on opacity: 0 or off-screen positioning to remove something from the tab order.

FAQ

Does the inert attribute fix this?

In browsers, inert removes a subtree from both the tab order and the accessibility tree, which is the right outcome. Probeo’s check reads tabindex and disabled but not inert, so an inert region that also carries aria-hidden="true" is still reported. Drop aria-hidden from an inert region, since inert already hides it.

aria-hidden on the icon hides only the icon. The link itself is not hidden, so it keeps its name and stays announced. The rule fails only when the hidden element is, or contains, the focusable thing.

What about a modal that hides the rest of the page?

A common pattern sets aria-hidden="true" on the page behind an open modal. That page still contains links and buttons, so the rule fails unless they are also taken out of the tab order. Using inert on the background, or the native <dialog> element with showModal(), handles both at once.

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.