Standards · ARIA

Role Window

dialog

Marks a container as a modal or non-modal dialog. Use the native <dialog> element with showModal() — it handles focus trapping, the top-layer, the backdrop, and Escape-to-close for free. Reach for role="dialog" only when the native element is impossible.

When to use

Use <dialog> opened via .showModal(). The native element is supported in every current browser, handles the inert background, the focus trap, Escape-to-close, the ::backdrop styling hook, and the top-layer rendering. It saves you about 200 lines of fragile JavaScript.

Reach for role="dialog" on a <div> only when:

  • You support browsers older than Chrome 37 / Firefox 98 / Safari 15.4 (unlikely in 2026).
  • The design demands a dialog that cannot live in the top-layer (rare).
  • You’re patching an inaccessible custom modal during a refactor.

Every dialog MUST have an accessible name (aria-labelledby pointing at a visible heading is the strongest pattern). Modal dialogs also need aria-modal="true".

Keyboard + focus contract

Per the APG dialog (modal) pattern:

  • On open, focus moves to the first focusable element inside the dialog (often the close button, or the primary input).
  • Tab and Shift+Tab cycle focus WITHIN the dialog — a “focus trap”. Tab from the last element returns to the first; Shift+Tab from the first returns to the last.
  • Escape closes the dialog.
  • On close, focus returns to the element that opened the dialog. Saving and restoring focus is non-negotiable; users who lose their place have no anchor.
  • The rest of the page is inert (or aria-hidden="true" + non-focusable as a fallback) while the dialog is open.

Common failures

  • Custom dialog with no focus trap. Tab steps out of the dialog and behind it; users lose context.
  • Focus not restored to the trigger on close. The next Tab lands on the document body.
  • Background content remains focusable through Tab even though it is visually hidden behind the dialog.
  • No accessible name. The dialog opens and the screen reader announces only “dialog”.
  • Custom dialog without aria-modal="true" — assistive tech treats it as inline content.
  • Escape closes the dialog, but the dialog is rendered through a portal that loses keyboard focus during the close animation.

Example

<!-- Preferred -->
<dialog id="confirm">
  <h2>Delete this draft?</h2>
  <p>This cannot be undone.</p>
  <form method="dialog">
    <button value="cancel">Cancel</button>
    <button value="delete" autofocus>Delete</button>
  </form>
</dialog>
<script>
  document.getElementById('confirm').showModal();
</script>

<!-- When <dialog> is impossible -->
<div role="dialog" aria-modal="true" aria-labelledby="dlgTitle">
  <h2 id="dlgTitle">Edit profile</h2>

</div>