CodePark/ questions / Focus management
START PRACTICINGPRACTICE
Frontend question · Accessibility › Focus management

How do you keep focus inside a modal dialog? Compare a hand-written focus trap, the inert attribute and the native <dialog> element with showModal().

<button id="open">Delete account</button>
<dialog id="confirm" aria-labelledby="confirm-title">
  <h2 id="confirm-title">Delete your account?</h2>
  <p>This cannot be undone.</p>
  <button id="cancel">Cancel</button>
  <button id="delete">Delete</button>
</dialog>

<script>
  const dialog = document.getElementById("confirm");
  document.getElementById("open").onclick = () => dialog.showModal();
  document.getElementById("cancel").onclick = () => dialog.close();
</script>

While a modal is open, focus must stay inside it. Otherwise Tab lands on page content the user can't see or use.

Three ways to do it:

1. Hand-written trap. Listen for Tab on the dialog. On the last focusable element, Tab moves focus to the first. On the first, Shift+Tab moves to the last. It works, but you must find every focusable element and keep the list up to date. Screen reader users can still reach the background with their own reading keys, so you also need aria-modal="true" or aria-hidden on the rest of the page.

2. inert on the background. Put inert on everything outside the dialog, for example on the app root. Inert content can't get focus, can't be clicked and is removed from the accessibility tree. This is simpler and stronger than a Tab trap. All major browsers support it.

3. Native <dialog> with showModal(). The browser does the hard parts:

  • the rest of the page becomes inert
  • focus moves into the dialog
  • Escape closes it
  • focus goes back to the opener when it closes

Tab can still reach the browser's own address bar, and that is expected. This is the best default today. You still give it a name with aria-labelledby, and you still test it.

  • Focus must stay inside an open modal and return to the opener when it closes.
  • A hand-written trap wraps Tab and Shift+Tab, but is easy to get wrong.
  • The inert attribute removes the background from focus, clicks and the accessibility tree.
  • <dialog> with showModal() makes the page inert, handles Escape and restores focus.
  • The dialog still needs an accessible name, for example with aria-labelledby.