Skip to content
Devix Open Source

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-index can 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", or role="alertdialog" for a danger tone, which tells a screen reader to announce it at once.
  • aria-labelledby and aria-describedby point 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 with aria-describedby, and the focus.
  • The busy state sets aria-busy on 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