back

Stop managing focus traps manually

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.

How it works

<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.

In practice

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.

What it replaces

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.