How to Trap Focus in Modal Dialogs Using HTML

Managing focus trapping inside modal dialogs has traditionally required complex JavaScript listeners to intercept keyboard navigation, but modern web development solves this natively using the HTML <dialog> element and the inert attribute. By utilizing the built-in browser APIs designed for accessibility, engineers can trap keyboard focus within an active dialog, prevent users from interacting with the background page, and automatically restore focus upon closing, all without third-party libraries.

The Native <dialog> Element and showModal()

The standard approach for native focus management relies on the HTML <dialog> element paired with the JavaScript method showModal().

When opened using dialog.showModal(), the browser automatically treats the element as a modal dialog and enforces the following behaviors:

  • Top-Layer Promotion: The dialog is rendered in the top layer of the DOM, displaying above all other elements regardless of their z-index.
  • Automatic Focus Trapping: Keyboard navigation via the Tab and Shift + Tab keys is strictly confined to focusable elements inside the dialog. The user cannot tab into the underlying document.
  • Initial Focus Allocation: The browser automatically focuses the first focusable element within the dialog (such as a button, input, or link).
  • Escape Key Handling: Pressing the Escape key automatically triggers a cancel event and closes the modal, releasing the focus trap.
<dialog id="userModal">
  <h2>Account Settings</h2>
  <input type="text" placeholder="Username" />
  <button id="closeBtn">Save</button>
</dialog>

<button id="openBtn">Open Modal</button>

<script>
  const modal = document.getElementById('userModal');
  const openBtn = document.getElementById('openBtn');
  const closeBtn = document.getElementById('closeBtn');

  openBtn.addEventListener('click', () => modal.showModal());
  closeBtn.addEventListener('click', () => modal.close());
</script>

Customizing Initial Focus with autofocus

If the first focusable element is not the ideal starting point—for example, if the first element is a destructive "Cancel" button or a generic close icon—engineers place the boolean autofocus attribute directly on the preferred child element inside the <dialog>. When showModal() runs, the browser will bypass the first element and target the element containing autofocus.

<dialog id="confirmModal">
  <button id="closeIcon">X</button>
  <p>Are you sure you want to proceed?</p>
  <!-- Focus starts here instead of the close button -->
  <button autofocus id="confirmBtn">Confirm</button>
</dialog>

Restoring Focus Automatically

When a dialog is closed using dialog.close(), the native implementation automatically returns keyboard focus to the triggering element that originally invoked showModal(). This ensures compliance with accessibility standards (WCAG) by maintaining continuity for screen readers and keyboard-only users without manual tracking of the previously focused node.

Trapping Focus with the inert Attribute

In scenarios where engineers cannot use <dialog> and must manage custom containers, the native inert global attribute is used. Adding inert to background elements removes them from the tab order and hides them from assistive technologies.

<main id="main-content" inert>
  <!-- All focusable items here are non-interactive -->
</main>

<div role="dialog" aria-modal="true">
  <!-- Interactive modal content -->
</div>

By toggling the inert attribute on all sibling elements or root containers outside the dialog, focus is inherently trapped within the active container, eliminating the need to write keydown event loops that manually calculate first and last focusable elements.