Building a modal means trapping focus inside it. The usual approach is a
JavaScript library that listens for Tab and
Shift+Tab, calculates focusable elements, and wraps around at
the boundaries. It works, but it's fragile. Add a dynamically rendered
button inside the modal and the trap might miss it. Remove the library and
the trap disappears.
The inert attribute solves this at the browser level.
<body>
<main inert>
<!-- Everything here becomes non-interactive -->
</main>
<dialog open>
<!-- This is the only interactive area -->
</dialog>
</body>
When you add inert to an element:
One attribute. No event listeners, no edge cases with dynamically added elements, no cleanup on unmount.
When you open a modal, set inert on the content behind it.
When you close it, remove it:
const content = document.querySelector("#main-content");
const dialog = document.querySelector("dialog");
function openModal() {
content.setAttribute("inert", "");
dialog.showModal();
}
function closeModal() {
content.removeAttribute("inert");
dialog.close();
}
In React:
<main inert={isModalOpen || undefined}>{/* page content */}</main>
Passing undefined when the modal is closed removes the
attribute from the DOM entirely.
focus-trap-react and similar librariesTab/Shift+Tab key listenersaria-hidden="true" on background content (which hides from
screen readers but doesn't prevent focus)
inert does all three at once. Hides from assistive tech,
blocks focus, blocks clicks.
Browser support is universal. Every major browser ships it. No polyfill needed.