Search topics
aria-hidden content that keyboard focus still reaches
A closed slide-out menu is moved off screen and given aria-hidden="true", which hides its links from screen readers but leaves every one of them in the tab order.
<nav class="drawer" aria-hidden="true"> The problem
Keyboard and screen reader users land on links nobody can see or hear.
A homeware shop has a Menu button in its header. It opens a menu that slides in from the left with four links: Shop, Journal, About and Contact. When the menu is closed, it waits just past the left edge of the screen. Press Tab from the Menu button and the focus ring disappears. Three more presses and it is still gone. On the fifth it comes back, on the first link in the page. Those four stops were the closed menu's links, out of view. A screen reader on the same four stops often says nothing at all. Some combinations of browser and screen reader read part of the link, so two people on the same page hear different things.
The closed menu is moved off screen with transform and given aria-hidden="true", so that screen readers skip it. aria-hidden does only that: it removes the menu from the accessibility tree. It does not take anything out of the tab order or stop a click. So focus lands on links whose name and role are not exposed to assistive technology, which fails 4.1.2 Name, Role, Value. WAI-ARIA forbids aria-hidden on anything that can take focus, for this reason. Because the menu is also out of view, the focus ring on those stops can't be seen, and the order runs through controls that are not there, so the same stops break focus visibility and focus order as well. The fixes below take the links out of the order while the menu is closed, which repairs all three.
The same failure turns up wherever aria-hidden hides something that focus can still reach: carousel slides out of view, tab panels behind the active tab, the page behind a custom dialog, and a repeated photo link in a product card. Mouse users never meet it, because nothing off screen can be clicked.
2 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited manipulation Keyboard users press Tab and focus vanishes off the edge of the screen for four presses, with nothing to show where it is or what Enter would do.
- Without vision Screen reader users land on links the screen reader was told to ignore: often it says nothing, sometimes only part of the link, so they can't tell where they are or what is there.
axe's aria-hidden-focus finds this in the state the page loads in: a closed menu with links inside it is reported. A scan doesn't open and close the menu, so content that only gets aria-hidden after an interaction is missed. When a modal is open, or a focus handler is attached, axe asks for a review instead of reporting a failure. The quick fix a scanner invites is to delete aria-hidden. The scanner then goes quiet, but the four invisible stops stay, now read out as links nobody can see. Another quick fix adds tabindex="-1" to the links in today's template, which holds until someone adds a link to the menu.
Try it
Press Tab from the Menu button and follow the focus.
With the menu closed, where does focus go after Menu, and what does a screen reader say there? Then open the menu and Tab through it.
Broken Can you fix this?
Screen reader says:
(Tab to the Menu button)
A recreated demo. The broken version is intentionally inaccessible.
The fix
Hide the closed menu from sight, Tab and screen readers together, with CSS or inert.
- 1Never put
aria-hidden="true"on anything that can take focus, or around it. - 2Hide what is out of use from everyone at once: CSS hiding or
inert, or every control inside out of the tab order. - 3Move focus out before you hide what holds it.
<button type="button" class="menu-button" aria-expanded="false" aria-controls="menu">Menu</button><nav id="menu" class="drawer" aria-label="Main" aria-hidden="true"> <a href="/shop">Shop</a> <a href="/journal">Journal</a> <a href="/about">About</a> <a href="/contact">Contact</a></nav>The closed menu hidden with CSS
<button type="button" class="menu-button" aria-expanded="false" aria-controls="menu">Menu</button><nav id="menu" class="drawer" aria-label="Main"> <a href="/shop">Shop</a> <a href="/journal">Journal</a> <a href="/about">About</a> <a href="/contact">Contact</a></nav>Why this fix: Hide things from everyone at once
transform hides the closed menu from sight and aria-hidden hides it from screen readers, but nothing hides it from the keyboard. Every link stays in the tab order, out of view and without a name a screen reader will read.
visibility: hidden hides the closed menu from sight, the keyboard, the pointer and screen readers at once, so aria-hidden is no longer needed. The delay on visibility lets the menu slide out before it is hidden, and the open state shows it straight away. When the menu doesn't animate, the hidden attribute does the same job: menu.hidden = !open.
Each of these passes. Hiding with CSS is the simplest when the closed menu is entirely out of view, which is the usual case: one rule, supported everywhere, and nothing for a script to forget. inert does the same job while leaving the content drawn, so choose it when part of what is out of use must still show, such as the edge of the next carousel slide or the page behind a custom dialog. Keeping aria-hidden with tabindex="-1" on each control is the smallest change to an existing component, but it has to be repeated for every control and reset on every open and close, so treat it as a stopgap until you can move to one of the others.
Move focus before you hide what holds it. If the menu closes while focus is on a link inside it, as it does with Escape or a close button in the menu, hiding the menu first leaves focus on an element nobody can reach. Browsers then drop focus to the start of the page. Put focus on the Menu button, then hide the menu. When the menu opens, show it first, by removing visibility: hidden or inert, and only then move focus into it.
An open menu that covers part of the page needs care too. If Tab can leave the menu for a link underneath it, focus lands where nobody can see it. Either let the open menu push the page aside, so nothing after it is covered, or make it modal: move focus in, make the rest of the page inert and close the menu on Escape, as in the modal-focus-management guide.
The same failure has different fixes depending on what is hidden.
- Carousel slides out of view:
inerton each slide that isn't shown, orvisibility: hiddenonce the slide has moved out. Check the carousel library's own option before patching its markup. - Tab panels behind the active tab: the
hiddenattribute on each panel that isn't selected. - The page behind a custom dialog:
inerton the page, as in themodal-focus-managementguide. - A repeated photo link in a product card that goes where the title link goes:
aria-hidden="true"withtabindex="-1"on that one link is fine, since it holds no other control, or join the photo and title into one link.
If the hidden content is on screen and meant to be used, the fix is the other way round: remove aria-hidden, so everyone gets it. And check the rendered page, not only the template: menus, carousels and cookie tools from third-party scripts often add aria-hidden themselves.
Fixing this with an AI coding assistant? Get this guide as Markdown
Verify the fix
5 checks, no mouse.
Test with: Keyboard, Screen reader
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared menu, carousel or dialog component changes.
Limits
This is a starting pattern, not a guarantee for every application. It covers content hidden with aria-hidden that focus can still reach. aria-hidden on something that can't take focus but carries information people need, such as a price or a warning, is a different failure that this guide doesn't fix. What a screen reader says on a hidden link varies between browsers and screen readers, so test with the ones your users have.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
References
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.