Skip to content

Accessibility

The Modal follows the WAI-ARIA APG Dialog (Modal) pattern — an interruptive window that takes over the page until the user acts on it — and meets WCAG 2.1 AA. An accessible modal announces itself to assistive technology as a dialog, traps focus inside itself while it is open, returns focus to the trigger when it closes, and gives its title, close button, and actions each an accessible name — so the task it holds is clear whether the user is looking at it, listening to it, or tabbing through it.

  • The modal surface, title, close button, and action buttons must maintain accessible contrast on every supported surface, including over the backdrop. Source: WCAG 1.4.3 Contrast (Minimum).
  • Meaning must never be conveyed by colour alone — pair it with text, an icon, or a shape. Source: WCAG 1.4.1 Use of Color.
  • Focus styles must stay visible on the close button and every action, meet contrast requirements on the modal surface, and be clearly distinguishable from hover. Source: WCAG 2.4.7 Focus Visible.
  • Use semantic colour tokens (e.g. foreground/neutral/default) rather than fixed values, so the component adapts correctly across themes and surfaces.

Always render the modal on a native <dialog> element and open it with showModal(). That single decision earns the whole APG Dialog contract from the browser: the dialog is promoted to the top layer (no z-index conflicts), a ::backdrop renders automatically, focus is trapped inside while it is open, focus returns to the previously focused element on close, and Escape closes it — with aria-modal="true" implied so the rest of the page drops out of the accessibility tree. To stop pointer events leaking to the page underneath, also set document.body.inert = true while the dialog is open and reset it on close, as shown in the Modal Dialog recipe. Wrap the content in a <form method="dialog"> so any close or confirm <button> dismisses the dialog and exposes its value via dialog.returnValue without custom script. The dialog’s, trigger’s, and close button’s roles, names, and states are in the Labelling elements section below.

Key Action
Tab / Shift + Tab Cycle focus through the focusable elements inside the dialog
Escape Close the dialog and return focus to the trigger

Interactive targets — the close button and every action — must be at least 44 px high for comfortable touch use, comfortably clearing the WCAG AA minimum. Source: WCAG 2.5.8 Target Size (Minimum).

Give every element the role, name, and state assistive technology needs. The demo stacks the trigger and the open dialog so the two can be read together — the content slot is a placeholder, since consumer-slotted content carries its own component’s semantics.

  1. Trigger
  2. Dialog
  3. Close button

Trigger

The button that opens the modal. Set aria-controls to the dialog’s id so the relationship is exposed, and give it a clear accessible name from its visible label — name the task it opens, not just “Open”.

Dialog

The native <dialog> carries the dialog role, and showModal() implies aria-modal="true". Name it with aria-labelledby pointing to the visible title (or aria-label when there is no visible title), so it never announces as an unnamed dialog. Add aria-describedby to reference a description paragraph when extra context helps.

Close button

Mandatory in every variant. Its accessible name must read as “Close” — from visible text, or aria-label="Close" when it is icon-only, with the close icon marked aria-hidden="true" so it is not announced twice.

Source: WCAG 4.1.2 Name, Role, Value.