Guide
Accessibility
The dialog is a native <dialog> opened with showModal(), so most of this is the browser's work
rather than ours — which is the point.
What you get from the element
- Focus is trapped inside the dialog, including the browser's own focus order. Nothing escapes to the page behind.
- Everything behind is inert — not just unclickable, but skipped by screen readers.
- Esc closes it, and we honour that as a cancel rather than blocking the key.
- The top layer puts it above every stacking context, so no
z-indexcan bury it. - Focus returns to whatever opened it when it closes.
- Nested dialogs stack properly: the newest is on top and the ones below stay inert.
What we add
role="dialog", orrole="alertdialog"for adangertone, which tells a screen reader to announce it at once.aria-labelledbyandaria-describedbypoint at the title and description.- The safest button has focus: a destructive dialog opens on Cancel, so a reflexive Enter cannot delete anything.
- Enter in a field confirms, and never closes the dialog without answering the promise.
- A failed field gets
aria-invalid, a message tied to it witharia-describedby, and the focus. - The busy state sets
aria-busyon the button that is working.
Keyboard
| Key | Does |
|---|---|
| Tab / Shift+Tab | Moves inside the dialog only. |
| Enter | Confirms — from a field or from the focused button. |
| Esc | Cancels, unless async work is in flight. |
Motion, colour and size
prefers-reduced-motion drops the entry transition and the busy spinner's rotation.
forced-colors mode draws real borders. On narrow screens the buttons stack full-width, in
reverse order, so the safe one is under the thumb.
Updated 12 Sep 2026