ADA Compliance Professionals

    Aria-hidden elements must not contain focusable content

    Last updated:

    Who it helps:
    Blind
    Mobility
    Standard:
    WCAG 2.2 Level A

    aria-hidden elements must not contain focusable content

    Elements with aria-hidden="true" cannot receive focus or contain any focusable descendants. This often appears in modals, menus, carousels, and decorative wrappers. Keyboard and screen reader users are affected when hidden regions take focus or hide interactive controls.

    Why It Matters

    When aria-hidden is true, the element and all its children are removed from the accessibility tree. If anything inside can still be focused, assistive technologies cannot perceive it, while keyboard users can still land on it. That split creates lost context, broken focus order, and inaccessible controls.

    People who navigate by keyboard (mobility impairments) may tab into a region that screen readers cannot announce. Screen reader users (blind or low-vision) miss controls entirely if they remain hidden from the accessibility API.

    Common Causes

    • Placing links, buttons, or inputs inside a container with aria-hidden="true".
    • Moving content off-screen with CSS (e.g., large negative positioning) but leaving it focusable.
    • Using aria-disabled instead of disabled on native controls, which does not remove focus.
    • Adding tabindex="0" (or leaving default focusability) within an aria-hidden region.
    • Trying to override a hidden ancestor with aria-hidden="false" on a child (it won’t re-expose the child to the accessibility API).
    • Toggling visibility with CSS only, without updating aria-hidden or focusability.

    How to Fix

    • Decide what should be hidden and from whom:
    • If content should be hidden from everyone, remove it from layout and focus order (hidden attribute or display:none). Avoid leaving focusable elements in the DOM.
    • If content must be visually present but ignored by assistive tech (rare), ensure an equivalent control is accessible elsewhere and make all descendants unfocusable.
    • Never place active, focusable controls inside an aria-hidden container. Move them outside or change the interaction model.
    • Make descendants unfocusable when necessary:
    • For custom elements: set tabindex="-1" and remove any JS that moves focus inside the hidden region.
    • For anchors: remove href (or replace with role="button" and tabindex="-1" when hidden) and re-enable properly when shown.
    • For native controls: use the disabled attribute (not aria-disabled) when appropriate.
    • Do not rely on aria-hidden="false" on a descendant to counter an ancestor’s aria-hidden="true". Toggle the ancestor or restructure the DOM.
    • Keep ARIA values valid and minimal: aria-hidden accepts only "true" or "false". Use role="presentation"/"none" for purely decorative elements.
    • Manage focus on show/hide:
    • When hiding a component (set aria-hidden="true" and/or hidden), move focus to the next logical control.
    • When showing it, set aria-hidden="false" and place focus on a meaningful element inside.

    Standards alignment (WCAG 2.2): 4.1.2 Name, Role, Value; 2.1.1 Keyboard; 2.4.3 Focus Order; 1.3.1 Info and Relationships.

    How to Test

    Keyboard check:

    • Tab through the page. Focus must never land inside elements with aria-hidden="true".
    • Open/close modals, menus, and drawers. When closed, their controls must not receive focus.

    Screen reader check:

    • With VoiceOver, NVDA, or JAWS, navigate by headings, landmarks, and tab. Hidden regions should not be announced. No controls should exist only inside aria-hidden containers.

    Mobile/touch check:

    • With TalkBack or VoiceOver, explore by touch and swipe. Hidden containers must not be announced or reachable.

    Code/DOM check:

    • Inspect elements with [aria-hidden="true"]. Ensure no descendants match focusable selectors: a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"]), [contenteditable="true"].

    Optional quick script to flag violations in dev tools:

    JS
    const hiddenRegions = document.querySelectorAll('[aria-hidden="true"]');
    const focusableSel = 'a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"]), [contenteditable="true"]';
    hiddenRegions.forEach(r => {
      const offender = r.querySelector(focusableSel);
      if (offender) console.warn('Focusable inside aria-hidden region:', offender);
    });

    Good Example

    A menu stays out of the accessibility tree and focus order while hidden, then becomes available and focusable when shown.

    HTML
    <button id="toggle">Menu</button>
    <nav id="site-menu" aria-hidden="true" hidden>
      <ul>
        <li><a href="/products">Products</a></li>
        <li><a href="/pricing">Pricing</a></li>
      </ul>
    </nav>
    <script>
    const btn = document.getElementById('toggle');
    const menu = document.getElementById('site-menu');
    btn.addEventListener('click', () => {
      const open = menu.hasAttribute('hidden');
      if (open) {
        menu.removeAttribute('hidden');
        menu.setAttribute('aria-hidden', 'false');
        menu.querySelector('a').focus();
      } else {
        menu.setAttribute('aria-hidden', 'true');
        menu.setAttribute('hidden', '');
        btn.focus();
      }
    });
    </script>

    Bad Example

    Focusable controls live inside an aria-hidden container, and a child tries to override the ancestor’s hidden state.

    HTML
    <aside id="promo" aria-hidden="true">
      <a href="/deal">Get the deal</a>
      <div aria-hidden="false">
        <button type="button">Apply now</button>
      </div>
    </aside>

    Quick Checklist

    • No focusable descendants inside elements with aria-hidden="true".
    • Hidden components use hidden or display:none when they contain interactive elements.
    • Do not use aria-disabled to remove focus; use disabled or tabindex="-1" appropriately.
    • Do not attempt to override an ancestor’s aria-hidden with a child.
    • Update aria-hidden and visibility together when showing/hiding UI.
    • Manage focus when toggling components open/closed.
    • Validate ARIA values and keep ARIA usage minimal and accurate.