Bulletproof Accessible Modals: Focus Trapping, aria-modal, Inert & ESC Key Handling
Master accessible modal dialog architecture. Learn how to manage focus trapping, use the inert attribute, handle the Escape key, and restore focus on close.
Modal dialogs are pervasive across web applications for sign-ups, confirmations, and alerts. Yet poorly implemented modals represent the single most common source of keyboard traps and screen reader confusion on the modern web. Building an accessible modal requires strict attention to **focus containment, background element isolation, ARIA labeling, and focus restoration**.
1. Focus shifts into the modal on open.
2. Tab navigation is strictly trapped inside the modal.
3. Pressing Escape immediately closes the modal.
4. Background content is inert and unclickable.
5. Focus returns to the exact trigger button when closed.
The 5 Non-Negotiable Rules of Modal Accessibility
When a modal opens, background elements must not be reachable via keyboard or screen reader virtual cursors. Failing to isolate the background allows screen reader users to wander off into the main document while visually obscured by the modal backdrop.
ARIA Roles and Attributes for Dialogs
| Attribute | Value | Purpose |
|---|---|---|
role |
"dialog" or "alertdialog" |
Identifies the container as a modal dialog. |
aria-modal |
"true" |
Tells assistive technologies that content outside is inert. |
aria-labelledby |
ID of the modal heading element | Provides the spoken title when the modal opens. |
aria-describedby |
ID of the modal description/body | Provides context or instructions for the dialog. |
Keyboard Focus Trapping & Tab Cycling
Pressing Tab on the last focusable element in the modal must wrap focus back to the first focusable control (usually the close button or first input). Pressing Shift + Tab on the first control wraps to the last control.
Modern Background Isolation with the HTML inert Attribute
The standard HTML inert attribute makes background content non-focusable, non-clickable, and invisible to screen readers without requiring complex tabIndex overrides:
// When opening modal:
document.getElementById('main-content').setAttribute('inert', '');
document.querySelector('header').setAttribute('inert', '');
document.querySelector('footer').setAttribute('inert', '');
// When closing modal:
document.getElementById('main-content').removeAttribute('inert');
document.querySelector('header').removeAttribute('inert');
document.querySelector('footer').removeAttribute('inert');
Production-Ready Accessible Modal in Vanilla JavaScript
function openModal(modalId, triggerEl) {
const modal = document.getElementById(modalId);
modal.removeAttribute('aria-hidden');
modal.classList.add('active');
// Set inert on background
document.getElementById('main-content').setAttribute('inert', '');
// Focus first control
const focusable = modal.querySelectorAll('button, input, select, textarea, [tabindex="0"]');
if (focusable.length) focusable[0].focus();
// ESC Key listener
function handleKeyDown(e) {
if (e.key === 'Escape') closeModal(modalId, triggerEl);
}
document.addEventListener('keydown', handleKeyDown);
}
Audit Your Website for WCAG 2.2 Compliance Today
Scan your domain in 60 seconds with Rogabot and get instant PR-ready code diffs to prevent ADA lawsuit exposure.