Warninga11y.aria-hidden-focus

Hidden from screen readers, still reachable by keyboard

Why it matters

aria-hidden="true" takes something out of what a screen reader reads, and nothing else: a link or button marked with it can still be reached with Tab. Someone using a screen reader then lands on a control that is announced as nothing, and can't tell where they are. The commonest case is the # link beside a heading, marked hidden because it is decoration and left in the tab order.

We judge only a link or control that is itself marked aria-hidden. One inside a hidden container is nearly always a closed menu or dialog that a stylesheet hides, which the HTML alone can't show, so we say nothing about those. To check them, press Tab with the menu closed: if the focus disappears, the menu needs inert.

Sources: WCAG 2.2, 4.1.2 Name, Role, Value (Level A), and the W3C's test rule for it, Element with aria-hidden has no content in sequential focus navigation.

How to fix it

Take it out of the tab order as well, or don't hide it:

<h2 id="pricing">Pricing <a href="#pricing" class="anchor" aria-hidden="true" tabindex="-1">#</a></h2>

For a closed menu or dialog, inert hides the whole thing from the keyboard and from screen readers together:

<nav class="mobile-menu" inert>…</nav>

And when it is meant to be used, it isn't decoration: remove aria-hidden.

Examples

These are the rule's own test pages, and what it says about each. They are run on every change to the rule book.

<h2 id="pricing">Pricing <a href="#pricing" class="anchor" aria-hidden="true">#</a>
</h2>

Warning1 link or control is marked aria-hidden="true" and still reached with Tab, where a screen reader announces nothing: a.anchor.

fail-heading-anchor
<nav class="menu" aria-hidden="true">
<a href="/">Home</a>
</nav>
<span aria-hidden="true">★</span>
<input type="checkbox" class="peer hidden" aria-hidden="true">
<div inert>
<a href="/" aria-hidden="true">Home</a>
</div>
<div class="d-none">
<a href="/x" aria-hidden="true">X</a>
</div>
<dialog>
<a href="#" aria-hidden="true">Close</a>
</dialog>
<details>
<summary>More</summary>
<a href="/y" aria-hidden="true">Y</a>
</details>
<fieldset disabled>
<button type="button" aria-hidden="true">Go</button>
</fieldset>

PassedNothing marked aria-hidden is left in the tab order (3 hidden elements).

pass-decoration
Example page
<nav class="menu" aria-hidden="true">
<a href="/">Home</a>
</nav>
<span aria-hidden="true">★</span>
<input type="checkbox" class="peer hidden" aria-hidden="true">
<div inert>
<a href="/" aria-hidden="true">Home</a>
</div>
<div class="d-none">
<a href="/x" aria-hidden="true">X</a>
</div>
<dialog>
<a href="#" aria-hidden="true">Close</a>
</dialog>
<details>
<summary>More</summary>
<a href="/y" aria-hidden="true">Y</a>
</details>
<fieldset disabled>
<button type="button" aria-hidden="true">Go</button>
</fieldset>
<h2 id="pricing">Pricing <a href="#pricing" class="anchor" aria-hidden="true" tabindex="-1">#</a>
</h2>
<button type="button" disabled aria-hidden="true">Buy</button>

PassedNothing marked aria-hidden is left in the tab order (2 hidden elements).

pass-taken-out