Accessibility Certified – Home

Modal dialog: focus in, focus out, nothing lost in between

A modal dialog blocks the rest of the page until it is closed. For keyboard and screen reader users that only works if focus is moved into the dialog and back out again cleanly.

2.4.32.1.24.1.22.4.112.1.1

Live demo

This demo is checked with axe-core without violations and fully operable by keyboard.

Try it with the keyboard: Tab, Enter, Space, the arrow keys and Escape. With a screen reader on, you hear what gets announced.

Unsubscribe from the newsletter?

You will no longer hear about new guides. You can sign up again at any time.

The live demo needs JavaScript. Code and requirements are below.

Keyboard interaction

Tab / Shift+Tab
Moves focus between elements inside the dialog; the page behind cannot be reached.
Escape
Closes the dialog; focus returns to the trigger.
Enter / Space
Activates the focused button.

When to use this pattern

Use a modal dialog for short tasks that need doing now: a confirmation or a quick input. Long forms, or content people want to read alongside the page, do not belong in a dialog.

The native <dialog> element with showModal() does much of the work: the rest of the page becomes inert, Escape closes the dialog and the browser moves focus into it. Still check that focus lands back on the trigger once the dialog closes.

Roles, states and properties

  • A native <dialog> opened with showModal(). For custom solutions: role="dialog" and aria-modal="true".
  • A name via aria-labelledby pointing at the dialog heading.
  • Optionally aria-describedby pointing at a short explanation.
  • A visible close button with an accessible name such as “Close”.
  • On open, focus goes to the first sensible element; on close, back to the button that opened the dialog.

Keyboard interaction

KeyEffect
Tab / Shift+TabMoves focus between elements inside the dialog; the page behind cannot be reached.
EscapeCloses the dialog; focus returns to the trigger.
Enter / SpaceActivates the focused button.

Code

HTML and JavaScript (shortened)
<button type="button" id="open">Cancel subscription</button>

<dialog id="dlg" aria-labelledby="dlg-h">
  <h2 id="dlg-h">Cancel your subscription?</h2>
  <p>You can subscribe again at any time.</p>
  <form method="dialog">
    <button value="no">Keep it</button>
    <button value="yes">Cancel subscription</button>
  </form>
</dialog>

<script type="module">
const dlg = document.getElementById('dlg');
const opener = document.getElementById('open');
opener.addEventListener('click', () => dlg.showModal());
dlg.addEventListener('close', () => opener.focus());
</script>

Common mistakes

  • Focus stays on the page behind when the dialog opens, so keyboard users operate an invisible background.
  • No way out: neither Escape nor a named close button (just an “×” with no text).
  • After closing, focus jumps to the top of the page instead of back to the trigger.
  • The background is only dimmed visually but still reachable by screen readers (missing aria-modal or inert).

Related WCAG criteria

Sources

Are your components built like this?

The free check finds many issues automatically. In the audit a person tests every component with a keyboard and a screen reader.

Free checkSee our method