aria-hidden="true" removes an element and all of its children from the accessibility tree, making them invisible to screen readers. Putting aria-hidden="true" on the <body> element (or any ancestor that wraps the entire page) hides everything from assistive technology — the page becomes completely unusable for screen-reader users while looking perfectly normal to everyone else.
<body> or <html>: the entire page disappears for screen-reader users — a total accessibility failure.aria-hidden="true" is found on <body>, <html>, or any element wrapping focusable content the user is currently interacting with.aria-hidden on <body> instead of on a sibling background container — accidentally hiding the modal too.aria-hidden bugs are some of the most severe.<dialog> element with showModal(), or the inert attribute on background siblings — never aria-hidden on an ancestor of focusable content.Think of aria-hidden="true" on <body>the way you'd think of locking the only door to your store while customers are still inside. Sighted users can't see anything wrong from the street — but anyone using a screen reader has been completely shut out.
Page-level aria-hidden bugs are uniquely destructive because they break every piece of content on the page at once — content, navigation, forms, footer. Specifically:
Page-level aria-hidden almost never gets written intentionally. It's a side effect of dynamic UI patterns where a developer needed to hide something and reached for aria-hidden on the wrong element.
aria-hidden="true" directly to <body> — which is an ancestor of the modal too, so the modal disappears along with everything else.aria-hidden during transition can fail to clean up if the transition is interrupted.<!-- BAD: modal opens by hiding the entire body -->
<body aria-hidden="true">
<div role="dialog">...modal content the user can't reach...</div>
</body>
<!-- BAD: splash screen never cleaned up -->
<body aria-hidden="true">
<main>The page loaded but is invisible to screen readers.</main>
</body>
<!-- GOOD: native <dialog> handles inert background automatically -->
<body>
<main id="page-content">...page content...</main>
<dialog id="confirm-dialog">
<h2>Confirm action</h2>
<button>Cancel</button>
<button>Confirm</button>
</dialog>
</body>
<script>
document.getElementById('confirm-dialog').showModal();
// Browser automatically marks everything outside the dialog as inert.
</script>
<!-- GOOD: inert on background siblings (not an ancestor of the modal) -->
<body>
<div id="app" inert>
<main>...background content, blocked from focus and screen readers...</main>
</div>
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm action</h2>
<button>Cancel</button>
<button>Confirm</button>
</div>
</body>
<!-- GOOD: scoped aria-hidden on a specific decorative sibling -->
<div aria-hidden="true" class="decorative-background-pattern"></div>
<main>Real content stays visible to screen readers.</main>Page-level aria-hiddendoesn't show up in normal QA — sighted users see nothing wrong. You need tools that read the accessibility tree directly.
aria-hidden="true" is set on <body>, <html>, or an ancestor of focusable content, with an AI-generated fix and an optional one-click GitHub PR.aria-hidden returns empty content and never gets cited.<body> node and check whether the "Accessibility Tree" shows it as hidden. Also useful for inspecting any modal or overlay state.axe-core — programmatic detection of hidden focusable elements in CI.<dialog> Element with showModal()The browser handles inert background automatically. Everything outside the dialog becomes non-focusable and effectively hidden, focus is trapped inside, and the Escape key dismisses. No aria-hidden on ancestors required.
inert Over aria-hidden for Background ContentThe inert attribute (now in all major browsers) hides content from screen readers and blocks pointer/keyboard interaction. aria-hidden alone allows tabbing into hidden content — a focus trap nightmare. Use inert on the background, not aria-hidden.
If you must use aria-hidden, set it on a sibling of the element you want to keep accessible — never an ancestor. A modal at the body level needs the OTHER body-level containers hidden, not <body> itself.
If your bootstrap code applies aria-hidden to mask flicker, write the cleanup in the same commit. Use finally blocks or a state machine so the attribute is removed even when init fails or is interrupted.
Use position: fixed with a high z-index so the banner overlays content. Don't hide the rest of the page — most jurisdictions don't require it, and it breaks accessibility. If you do need a blocking modal, use the native <dialog> pattern.
If your SPA marks the outgoing route as aria-hidden during animation, write an integration test that confirms the attribute is gone after navigation completes. Interrupted transitions are the most common source of stuck aria-hidden in single-page apps.
Check the library's source for how it hides the background. If it touches <body> directly with aria-hidden, look for an alternative or wrap it. Modern libraries (Radix UI, Headless UI, Reach UI) use inertor scoped containers — older ones often don't.
If an element has keyboard focus, applying aria-hidden="true"creates a trapped, invisible focus state — the user's screen reader silently "is" somewhere they can't hear or escape from. Move focus first, then hide.
aria-hidden="true" Stuck on <body> in ProductionWhat's happening: A modal, splash screen, or cookie banner set the attribute and didn't clean up. The whole page is hidden from screen readers.
Fix: Find the code that sets aria-hidden on <body> (search your codebase for document.body.setAttribute("aria-hidden"). Replace with the native <dialog> pattern or apply inert to specific sibling containers instead.
What's happening: The library applies aria-hidden="true" to the modal's parent — which is also an ancestor of the modal — so the modal itself disappears.
Fix: Move the modal to a portal at the body level, then apply inert to the main app container only. Native <dialog> handles this automatically.
aria-hidden on Focused ElementsWhat's happening: A previously-focused button got hidden, but focus stayed on it. The screen reader is "on" an invisible element with no announcement.
Fix: Move focus to a known visible element before hiding the previous one. In modal close handlers, return focus to the trigger button.
aria-hidden Instead of inert for Modal BackgroundsWhat's happening: The background is hidden from screen readers, but a Tab keypress still moves focus into the "hidden" tree — creating an unreachable, invisible focus state.
Fix: Replace aria-hidden="true" with inert on the background container. inert blocks both screen-reader access AND focus, eliminating the dead-zone bug.
display: none vs. visibility: hiddenFour mechanisms can "hide" content, and each has different effects on the accessibility tree, layout, and keyboard focus. Choose based on what you need to hide and from whom.
| Mechanism | Hides from Screen Readers? | Blocks Keyboard Focus? | Best Used For |
|---|---|---|---|
aria-hidden="true" | Yes | No (focus still reachable) | Decorative siblings only — never on ancestors of focusable content. |
inert attribute | Yes | Yes | Modal backgrounds, off-screen panels, anything users shouldn't reach. |
display: none | Yes | Yes | Truly removed from the page; no layout space; no transitions. |
visibility: hidden | Yes | Yes | Reserves layout space but invisible — useful for measuring or animating in. |
<body> a problem?Because aria-hidden="true" removes the element and every descendant from the accessibility tree. <body> is the ancestor of every visible element on the page, so screen readers see nothing. The page becomes completely unusable for assistive-technology users while looking normal to everyone else.
Use the native <dialog> element with showModal() — the browser handles inert background automatically. If you can't use <dialog>, apply the inert attribute to background sibling containers (never ancestors of the modal). inert blocks both screen-reader access and keyboard focus, while aria-hidden only does the former.
Yes — on decorative elements that have no semantic value and no focusable descendants. Examples: a decorative SVG icon next to a labeled button, an image-only background pattern, or a duplicate visual cue that's announced by a screen-reader-friendly label. Never on ancestors of focusable content.
Google's helpful-content evaluation treats accessibility failures as a ranking signal — and a fully hidden page is the most severe failure possible. Indirectly, aria-hidden on <body>doesn't hide content from Googlebot (Googlebot reads the DOM, not the accessibility tree), but the WCAG failure pattern correlates with lower rankings in competitive queries.
Yes. AI search engines parse the accessibility tree to understand page structure, headings, and CTAs. A page with aria-hidden="true" on <body> returns an empty accessibility tree — the bot sees nothing extractable and the page is never cited. This is a particularly cruel failure mode because the page might rank fine in traditional search but be invisible in generative answers.
aria-hidden="true" hides content from the accessibility tree but does NOT block keyboard focus — Tab can still reach inside, creating a trapped, invisible focus state. inert hides from the accessibility tree AND blocks all interaction (focus, pointer events, screen-reader access). For modal backgrounds and any case involving focusable content, always prefer inert.
Run a Greadme deep scan for a single-page check, or the Greadme crawler scan to sweep every indexable page on your site. Both flag aria-hidden="true" on <body>, <html>, or any ancestor of focusable content, and pair each finding with an AI-generated fix and an optional one-click GitHub PR. You can also inspect the accessibility tree manually in Chrome DevTools.
aria-hidden="true" on <body> is one of the most severe accessibility bugs that can ship to production — and one of the easiest to miss, because the page looks fine to everyone except screen-reader users. The fix is almost always architectural: stop hiding ancestors, prefer the native <dialog> pattern, and use inert on background siblings when you need to block focus.
Run a Greadme deep scan to find every page on your site where aria-hidden is set on the body or wraps focusable content — each issue comes with an AI-generated fix and a one-click GitHub PR.