OverlaysFlagship guide
Accessible Dialog (Modal)
A window overlaid on the page, blocking interaction with the rest of the app until dismissed.
Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.
Implementation
Focus trap via Tab/Shift+Tab interception, Escape handling, and explicit focus restoration.
<button id="dialog-trigger">Edit profile</button>
<!-- Modal dialog: role="dialog" + aria-modal="true" + aria-labelledby
(the title). Starts hidden. Tip: a native <dialog> opened with
showModal() gives you the focus trap, Escape, and an inert
background for free, this manual version shows what that does. -->
<div id="dialog-overlay" class="overlay" hidden>
<div id="dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Edit profile</h2>
<button id="dialog-close" aria-label="Close">×</button>
<!-- dialog content / form fields -->
</div>
</div>Required roles, states & properties
| Element | Attribute | Why |
|---|---|---|
| Dialog container | role="dialog" | Identifies the element as a dialog so AT announces "dialog" and switches some screen readers into a more linear reading mode. |
| Dialog container | aria-modal="true" | Signals that content outside the dialog is inert. Combined with, not a replacement for, a real focus trap. |
| Dialog container | aria-labelledby | Points at the visible heading so the dialog's accessible name matches what's on screen (SC 2.5.3 Label in Name). |
| Dialog container | aria-describedby | Optionally points at supporting body text so it's announced right after the name/role on open. |
| Close button | aria-label="Close dialog" | An icon-only close button has no accessible name from its text content, so one must be supplied explicitly. |
Keyboard interaction model
| Key | Behavior |
|---|---|
| Tab | Moves focus to the next focusable element inside the dialog. Wraps from the last to the first (focus trap). |
| Shift+Tab | Moves focus to the previous focusable element inside the dialog. Wraps from the first to the last. |
| Escape | Closes the dialog and returns focus to the triggering element. |
| Enter / Space | Activates the focused button (native behavior, no custom handling needed). |
Focus management rules
- On open: focus moves to the first focusable element inside the dialog (here, the close button).
- While open: Tab/Shift+Tab cycle only within the dialog's focusable elements.
- On close (Escape, confirm, cancel, or scrim click): focus returns to the element that opened the dialog.
- If the trigger element no longer exists after close (e.g. it was in a list row that got deleted), move focus to the next logical element, never let it fall back to <body>.
WCAG 2.2 success criteria mapping
| SC | Name | Level | Why it applies |
|---|---|---|---|
| 2.1.1 | Keyboard | A | All functionality is operable through a keyboard interface with no specific timing. |
| 2.1.2 | No Keyboard Trap | A | Keyboard focus can always be moved away from a component using standard navigation. |
| 2.4.3 | Focus Order | A | Focusable components receive focus in an order that preserves meaning and operability. |
| 2.4.7 | Focus Visible | AA | Any keyboard-operable UI has a visible focus indicator. |
| 4.1.2 | Name, Role, Value | A | For all UI components, name, role, and value are programmatically determinable; states and changes are announced. |