Hidden content needs to stay out of the focus order
- Rule:
axe/aria-hidden-focus - Checks: ARIA hidden element must not be focusable or contain focusable elements
- WCAG: 4.1.2
- Fix class:
mechanical: A fix needs no authored text or visual decision.
Who it affects
Section titled “Who it affects”- People who use a screen reader or a braille display, most of them blind.
- People who use a keyboard or a switch device instead of a mouse.
How to fix it
Section titled “How to fix it”Content behind an open modal dialog has aria-hidden="true"
Section titled “Content behind an open modal dialog has aria-hidden="true"”Make that content inert instead, which hides it from assistive technology and takes it out of the focus order, and remove inert when the dialog closes. A <dialog> opened with showModal() does this for you.
<div class="page" inert> <a href="/account">Account</a></div><div role="dialog" aria-modal="true" aria-labelledby="confirm-title"> <h2 id="confirm-title">Delete this order?</h2> <button type="button">Delete</button> <button type="button">Cancel</button></div>A closed menu, collapsed panel or off-screen slide has aria-hidden="true" but its links can still take focus
Section titled “A closed menu, collapsed panel or off-screen slide has aria-hidden="true" but its links can still take focus”Hide it from everyone while it is closed, with the hidden attribute or display: none, or make it inert. Remove that when it opens.
<ul id="account-menu" hidden> <li><a href="/orders">Orders</a></li></ul>A duplicate link is hidden from screen readers, such as a card’s image link beside its text link
Section titled “A duplicate link is hidden from screen readers, such as a card’s image link beside its text link”Combine the image and the text into one link. Where they must stay separate, also give the hidden link tabindex="-1" so keyboard users skip it.
<a href="/products/kettle"> <img src="kettle.jpg" alt=""> Copper kettle</a>Fixes that fail
Section titled “Fixes that fail”Adding aria-hidden="false" to the focusable children
Section titled “Adding aria-hidden="false" to the focusable children”It does not undo aria-hidden="true" on an ancestor, so the children stay hidden from assistive technology while keyboard focus still reaches them.
Removing aria-hidden from content that should stay hidden
Section titled “Removing aria-hidden from content that should stay hidden”The rule passes, but screen readers then read a closed menu or the page behind a dialog. Hide it from everyone or make it inert instead.
In React
Section titled “In React”The part of the fix that differs in React, in its syntax. The rest of the guidance holds.
A modal component sets aria-hidden on the app root while it is open
Section titled “A modal component sets aria-hidden on the app root while it is open”Render a <dialog> and open it with showModal() through a ref in an effect, which makes the rest of the page inert for you. For a custom overlay rendered with createPortal, set the boolean inert prop on the app content from the same state instead.
function ConfirmDialog({ open, onClose, onConfirm }) { const ref = useRef(null); const titleId = useId();
useEffect(() => { const dialog = ref.current; if (open && !dialog.open) dialog.showModal(); if (!open && dialog.open) dialog.close(); }, [open]);
return ( <dialog ref={ref} aria-labelledby={titleId} onClose={onClose}> <h2 id={titleId}>Delete this order?</h2> <button type="button" onClick={onConfirm}> Delete </button> <button type="button" onClick={onClose}> Cancel </button> </dialog> );}A closed menu or inactive slide stays mounted, hidden only with aria-hidden
Section titled “A closed menu or inactive slide stays mounted, hidden only with aria-hidden”Unmount it while it is closed, or set hidden or inert from state, such as inert={!active}. React 19 renders inert as a boolean attribute.
function Slide({ active, children }) { return ( <div className="slide" inert={!active}> {children} </div> );}Fixes that fail in React
Section titled “Fixes that fail in React”Writing inert="" or inert="false"
Section titled “Writing inert="" or inert="false"”React 19 treats inert as a boolean: an empty string renders no attribute, and the string "false" is truthy, so it makes the content inert. Pass true or false.
Rendering <dialog open={isOpen}>
Section titled “Rendering <dialog open={isOpen}>”The open attribute shows the dialog without making it modal, so the page behind stays reachable by keyboard and screen readers. Call showModal() instead.
More on the rule
Section titled “More on the rule”axe-core’s documentation of the rule
CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.