ARIA Hidden On A Focusable Element
ID |
html.aria_hidden_focusable |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Html |
Tags |
accessibility, reliability |
Rationale
aria-hidden removes an element from the accessibility tree but not from the tab order. A focusable
element hidden this way becomes a focus stop with nothing to announce: the user tabs onto it, the
screen reader has no node to read, and the keyboard focus appears to vanish. This is the one
combination the ARIA specification calls out as never valid.
<button aria-hidden="true">Save</button> <!-- FLAW — reachable by Tab, unreadable there -->
<div tabindex="0" aria-hidden="true">x</div> <!-- FLAW — tabindex puts it back in the order -->
<a href="/help" aria-hidden="true">Help</a> <!-- FLAW — a link with an href is focusable -->
<button aria-hidden="true" tabindex="-1">Save</button> <!-- OK — hidden and out of the order -->
<span aria-hidden="true">×</span> <!-- OK — decoration, takes no focus -->
<a aria-hidden="true">No href</a> <!-- OK — not a link without one -->
<button aria-hidden="${collapsed}">Save</button> <!-- OK — settled at render time -->
Focusability is read as written, and the tabindex decides when there is one: a negative tabindex
takes even a button out of the tab order — which is exactly the fix — while a non-negative one puts
any element into it. With no tabindex, the tag decides: the interactive controls, the links that
carry an href, and the embedded-content tags.
Only a literal true is reported. A valueless aria-hidden, or an empty one, is the property’s
undefined state and does not hide the element at all, so it is not a finding.
Remediation
Add tabindex="-1" so the element leaves the tab order along with the accessibility tree, or drop
aria-hidden and hide the element with display: none or the hidden attribute, both of which
remove it from both. If the element has to stay operable, give it an accessible name instead of
hiding it.