Modal dialogs trap focus, restore focus when closed, and use role="dialog" with aria-label
Why it matters
Modal dialogs that do not trap keyboard focus allow Tab to move focus outside the modal into background content that is visually obscured, leaving keyboard users unable to see or interact with the focused element. Modals without role="dialog" are not announced as dialogs by screen readers, so users receive no cue that a blocking overlay has appeared. When focus fails to return to the trigger element on close, users lose their place in the page entirely. WCAG 2.2 SC 2.1.1 (Level A) and SC 4.1.2 (Level A) both apply; Section 508 2018 Refresh 502.3.2 requires that the full dialog lifecycle be accessible to AT. Broken modal focus is one of the most common Section 508 findings in government application audits.
Severity rationale
Medium because broken modal focus management disrupts keyboard and screen reader users at a critical interaction point, but it does not prevent access to content outside the modal.
Remediation
Implement focus trapping and restoration in src/components/Modal.tsx. Store a ref to the trigger element before opening, move focus to the first focusable element on open, and return focus on close:
const triggerRef = useRef<HTMLButtonElement>(null);
const firstFocusRef = useRef<HTMLButtonElement>(null);
const openModal = () => {
setIsOpen(true);
// Move focus after render
requestAnimationFrame(() => firstFocusRef.current?.focus());
};
const closeModal = () => {
setIsOpen(false);
triggerRef.current?.focus(); // Restore to trigger
};
{isOpen && (
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<h2 id="modal-title">Confirm deletion</h2>
<button ref={firstFocusRef} onClick={closeModal}>Cancel</button>
<button onClick={handleDelete}>Delete</button>
</div>
)}
Use a library like focus-trap-react for robust cross-browser focus trapping rather than hand-rolling the Tab key handler.
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Section 508 Accessibility Audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- wcag:2.2 · 2.1.1 — Keyboard
- wcag:2.2 · 4.1.2 — Name, Role, Value
- section-508:2018-refresh · 502.3.2 — Modification of User Interface Elements — Name Role Value
Taxons
History
- 2026-04-18·v1.0.0·Initial import from gov-section-508·automated