All rules

Sheath rule

a11y-no-aria-hidden-on-focusable

Elements with aria-hidden="true" must not be focusable or contain focusable descendants.
Package
Core
Default severity
error by default
Auto-fix
Auto-fix available

with --dangerous (the fix removes the attribute)

#Why

Using aria-hidden="true" on an element that can receive focus creates a confusing experience for screen reader users. The element is hidden from the accessibility tree but can still be reached via keyboard navigation, creating a "ghost" element that users can interact with but cannot understand.

The same problem applies to every descendant because aria-hidden removes the whole subtree from the accessibility tree. A descendant cannot undo an ancestor's aria-hidden="true" by setting aria-hidden="false".

#Examples

#Bad

<!-- Focusable elements with aria-hidden -->
<button aria-hidden="true">Hidden Button</button>
<a href="/page" aria-hidden="true">Hidden Link</a>
<input type="text" aria-hidden="true">
<select aria-hidden="true"><option>Option</option></select>
<!-- Elements made focusable via tabindex -->
<div aria-hidden="true" tabindex="0">Focusable div</div>
<span aria-hidden="true" tabindex="0">Focusable span</span>
<!-- Contenteditable with aria-hidden -->
<div aria-hidden="true" contenteditable="true">Editable content</div>
<div aria-hidden="true" contenteditable="plaintext-only">Editable content</div>
<!-- Focusable descendants are hidden too -->
<div aria-hidden="true">
<button>Hidden descendant</button>
</div>

#Good

<!-- Hidden and not focusable -->
<div aria-hidden="true">Decorative content</div>
<span aria-hidden="true"><i class="icon"></i></span>
<!-- Focusable but not hidden -->
<button>Visible Button</button>
<a href="/page">Visible Link</a>
<!-- Disabled elements are not focusable -->
<button aria-hidden="true" disabled>Disabled button</button>
<!-- Hidden inputs are not focusable -->
<input type="hidden" aria-hidden="true" name="context" value="checkout">
<!-- Links without href are not focusable -->
<a aria-hidden="true">Not a link</a>
<!-- Negative tabindex is the sanctioned remediation: it takes the native
control out of the tab order, matching what aria-hidden promises -->
<button aria-hidden="true" tabindex="-1">Decorative button</button>
<a href="/page" aria-hidden="true" tabindex="-1">Hidden link</a>
<!-- Every descendant is outside sequential focus navigation -->
<div aria-hidden="true">
<button disabled>Disabled button</button>
<a href="/page" tabindex="-1">Hidden link</a>
</div>

#Focusable Elements

These elements are focusable by default:

  • <a> (with href)
  • <area> (with href)
  • <button>
  • <input>
  • <select>
  • <textarea>
  • <details> and the first <summary> child that acts as its disclosure control
  • <audio>/<video> (with controls)
  • <iframe>
  • Elements with tabindex="0" or positive tabindex
  • Elements with contenteditable="true", contenteditable="plaintext-only", or an empty contenteditable

#Notes

  • display: none and visibility: hidden remove the element from the page and the accessibility tree together, so focus cannot reach it
  • Disabled form controls (button, input, select, and textarea) are not focusable, so aria-hidden is safe on them. A disabled attribute has no such effect on links or arbitrary elements
  • Hidden inputs are not focusable. tabindex follows HTML integer parsing, so leading ASCII whitespace is ignored and a numeric prefix such as the 1 in 1.5 still determines focusability
  • An explicit negative tabindex (-1, -2, ...) on a natively focusable element removes it from the tab order, so aria-hidden is safe there too. This pairing correctly hides a decorative control
  • A standalone <summary>, or a later <summary> after an unconditional first summary child, is not a native disclosure control. An explicit nonnegative tabindex can still make it focusable.
  • For interactive elements, use the disabled attribute, add tabindex="-1", or remove the href from links

#References

#Related Rules