Skip to content

The body needs to stay exposed to assistive technology

  • Rule: axe/aria-hidden-body
  • Checks: aria-hidden=“true” must not be present on the document body
  • WCAG: 1.3.1, 4.1.2
  • Fix class: mechanical: A fix needs no authored text or visual decision.
  • People who use a screen reader or a braille display, most of them blind.

Script hides the page behind a modal dialog with aria-hidden="true" on <body>

Section titled “Script hides the page behind a modal dialog with aria-hidden="true" on <body>”

Hide the dialog’s surroundings, not <body>: keep the dialog outside the page’s wrapper and set inert on the wrapper while the dialog is open. A native <dialog> opened with showModal() does this for you. On <body>, aria-hidden hides the whole page, the dialog included.

<body>
<div class="page" inert>
<main>
<h1>Checkout</h1>
</main>
</div>
<div role="dialog" aria-modal="true" aria-labelledby="signin-title">
<h2 id="signin-title">Sign in to continue</h2>
<button type="button">Close</button>
</div>
</body>

The attribute stays after the overlay that set it has closed, or was copied in with a template

Section titled “The attribute stays after the overlay that set it has closed, or was copied in with a template”

Remove it, and remove any hiding in the same code that closes the overlay, so the page is never left hidden.

axe passes, but ARIA cautions against "false" because browsers handle it inconsistently. Remove the attribute instead.

Moving aria-hidden="true" to a wrapper that also holds the dialog

Section titled “Moving aria-hidden="true" to a wrapper that also holds the dialog”

The dialog is hidden along with the page, so screen reader users land in a dialog nothing announces. Hide only the dialog’s surroundings.

Hiding the surroundings with aria-hidden but leaving them focusable

Section titled “Hiding the surroundings with aria-hidden but leaving them focusable”

Keyboard users can still tab out of the dialog into content screen readers will not announce. inert removes both focus and the content from the accessibility tree.

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 document.body from an effect

Section titled “A modal component sets aria-hidden on document.body from an effect”

Render the dialog with createPortal into document.body, outside the element that wraps the rest of the app, and set inert={open} on that wrapper; React 19 writes inert as a boolean attribute. Or render a native <dialog> and call showModal() on it from an effect.

import { useId } from 'react';
import { createPortal } from 'react-dom';
function SignInLayout({ dialogOpen, children }) {
const titleId = useId();
return (
<>
<div className="page" inert={dialogOpen}>
{children}
</div>
{dialogOpen &&
createPortal(
<div role="dialog" aria-modal="true" aria-labelledby={titleId}>
<h2 id={titleId}>Sign in to continue</h2>
<button type="button">Close</button>
</div>,
document.body
)}
</>
);
}

Setting aria-hidden on the app’s root element while the dialog renders inside it

Section titled “Setting aria-hidden on the app’s root element while the dialog renders inside it”

The dialog is a descendant of the root, so it is hidden along with the page. Portal it outside the hidden element.

Setting the attribute in an effect with no cleanup

Section titled “Setting the attribute in an effect with no cleanup”

If the dialog unmounts another way, such as on a route change, nothing removes the attribute and the page stays hidden.

axe-core’s documentation of the rule

CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.