Overlays
Alert & Alert Dialog
A message interrupting the user with important, often time-sensitive, information requiring acknowledgment.
Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.
Implementation
role="alertdialog", focus trap, Escape handling, and focus forced to Cancel on open.
alert-dialog-pattern.tsx
function AlertDialogPattern({ title, confirmLabel, cancelLabel }) {
const [open, setOpen] = useState(false);
const dialogRef = useRef(null);
const cancelBtnRef = useRef(null); // least destructive action
const triggerRef = useRef(null);
useEffect(() => {
if (!open) return;
cancelBtnRef.current?.focus(); // NEVER focus the destructive button
function onKeyDown(e) {
if (e.key === "Escape") { close(); return; }
if (e.key !== "Tab") return;
// ...manual focus trap, same as Dialog...
}
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [open]);
function close() {
setOpen(false);
triggerRef.current?.focus();
}
return (
<>
<button ref={triggerRef} onClick={() => setOpen(true)}>Delete 12 items</button>
{open && (
<div className="overlay"> {/* no click-outside-to-close */}
<div ref={dialogRef} role="alertdialog" aria-modal="true" aria-labelledby="title">
<h2 id="title">{title}</h2>
<button ref={cancelBtnRef} onClick={close}>{cancelLabel}</button>
<button onClick={close}>{confirmLabel}</button>
</div>
</div>
)}
</>
);
}Live demo
Required roles, states & properties
| Element | Attribute | Why |
|---|---|---|
| Dialog container | role="alertdialog" | Distinct from role="dialog" — tells AT this interruption carries urgent, often destructive, information requiring explicit acknowledgment. |
| Dialog container | aria-modal="true" | Same as Dialog: marks background content inert, backed by a manual focus trap. |
| Dialog container | aria-labelledby | Points at the visible heading so the accessible name matches on-screen text. |
| Dialog container | aria-describedby | Points at the consequence text (e.g. "This can't be undone") so it's read immediately after the name — critical context for a destructive confirmation. |
| Cancel button | Receives focus on open | APG requirement specific to alertdialog: default focus goes to the least destructive action so accidental Enter/Space never confirms a destructive one. |
Keyboard interaction model
| Key | Behavior |
|---|---|
| Tab | Moves focus to the next focusable element inside the dialog. Wraps from last to first. |
| Shift+Tab | Moves focus to the previous focusable element. Wraps from first to last. |
| Escape | Closes the dialog without taking the destructive action (equivalent to Cancel) and returns focus to the trigger. |
| Enter / Space | Activates whichever button has focus. Because focus starts on Cancel, this is safe by default. |
Focus management rules
- On open: focus moves to the least destructive action (typically Cancel) — never to the confirm/destroy button.
- While open: Tab/Shift+Tab cycle only within the dialog's focusable elements.
- On close (Escape, confirm, or cancel): focus returns to the element that opened the dialog.
- Unlike a plain Dialog, clicking the scrim/overlay does not close a true alert dialog — the interruption should be resolved deliberately, not brushed aside accidentally.
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. |
| 4.1.2 | Name, Role, Value | A | For all UI components, name, role, and value are programmatically determinable; states and changes are announced. |
| 4.1.3 | Status Messages | AA | Status messages can be programmatically determined via role or properties (e.g. aria-live) without receiving focus. |