Building Accessible Dialogs From Scratch
Dialog overlays are one of the most common user interface patterns, yet they are frequently built with severe accessibility flaws. When screen reader users or keyboard-only navigators encounter an inaccessible modal, they often get trapped or lose context entirely.
Vocabulary
| Term | Meaning |
|---|---|
| Focus trap | Keeps keyboard focus inside the open dialog |
| Focus restoration | Returns focus to the trigger element when the dialog closes |
| WCAG 2.2 AA | Web accessibility standard this pattern targets |
| aria-modal | Tells assistive tech the background is inert |
| aria-labelledby | Links the dialog to its visible title for screen readers |
Steps
Step 1 — Meet the WCAG checklist
To meet WCAG 2.2 AA standards, an interactive dialog must implement:
- Focus Trapping: Keyboard focus must remain confined inside the active dialog. Users should not be able to tab back into the background document.
- Focus Restoration: When the dialog closes, focus must return to the trigger element that launched it.
- Esc Key Closing: Pressing the
Escapekey must close the active dialog immediately. - ARIA Attributes: The modal container requires
role="dialog",aria-modal="true", and must be labelled by headings viaaria-labelledby.
Step 2 — Implement a focus trap in vanilla TypeScript
Here is a clean implementation of a focus trap utility. It queries all focusable child elements in the dialog, tracks the first and last items, and loops the focus during keyboard tabbing.
Common pitfalls
- Opening a dialog without moving focus inside — screen reader users lose context
- Forgetting focus restoration — keyboard users land nowhere after close
- Missing
aria-labelledby— dialog purpose is unclear to assistive tech - Trap without Escape handler — users feel stuck
Verify it works
Open the dialog with keyboard only. Tab through all controls — focus should never leave the dialog. Press Escape — dialog closes and focus returns to the trigger. Run an axe audit — no critical violations on the modal.
Takeaway
Focus trap, Escape to close, and focus restoration are non-negotiable for accessible dialogs. Primitives like Radix UI encode this for you — but knowing the mechanics helps you debug when something breaks.