Accessibility pattern · Overlays

Modal dialog

showModal() does the hard parts: the rest of the page becomes inert, focus moves into the dialog, and Escape closes it. What is left is naming it, describing it, and sending focus back to the button that opened it.

WCAG criteria
7
Keyboard rules
4
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
Enter or SpaceOn the trigger button, opens the dialog and moves focus to its first field.
Tab or ShiftTabMoves through the dialog's controls only; the page behind cannot be reached.
EscapeCloses the dialog without saving and returns focus to the trigger.
EnterIn a field, submits the form, which saves and closes the dialog.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
The dialog opensEdit profile, dialog. Your name and bio appear on your public page. Display name, edit text, Asha Rao
Focus reaches the close buttonClose, button
The dialog closesEdit profile, button (focus is back on the trigger)
Changes are savedProfile saved

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-modal-demo" data-ap-modal>
  <button type="button" class="ap-btn ap-btn--primary" data-ap-open aria-haspopup="dialog">
    <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>
    Edit profile
  </button>
  <p class="ap-modal-demo__status" role="status"></p>

  <dialog class="ap-modal" aria-labelledby="modal-name" aria-describedby="modal-desc">
    <form class="ap-modal__form" method="dialog">
      <div class="ap-modal__top">
        <h2 class="ap-modal__name" id="modal-name">Edit profile</h2>
        <button type="button" class="ap-modal__close" data-ap-close aria-label="Close">
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6 6 18"/></svg>
        </button>
      </div>
      <p class="ap-modal__desc" id="modal-desc">Your name and bio appear on your public page.</p>
      <div class="ap-field">
        <label class="ap-label" for="modal-display-name">Display name</label>
        <input class="ap-input" id="modal-display-name" name="name" autocomplete="nickname" value="Asha Rao" autofocus />
      </div>
      <div class="ap-field">
        <label class="ap-label" for="modal-bio">Bio</label>
        <textarea class="ap-input" id="modal-bio" name="bio" rows="3">Product designer in Pune.</textarea>
      </div>
      <div class="ap-modal__actions">
        <button type="button" class="ap-btn" data-ap-close>Cancel</button>
        <button type="submit" class="ap-btn ap-btn--primary" value="save">Save changes</button>
      </div>
    </form>
  </dialog>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    The dialog has a name from its heading and a description from its first paragraph, so the relationship is exposed, not implied.

  • 2.1.1 Keyboard Level A

    Every control, including closing, works from the keyboard; Escape is handled by the browser.

  • 2.1.2 No Keyboard Trap Level A

    Focus is held in the dialog while it is open, but Escape, Cancel and the close button always let it out.

  • 2.4.3 Focus Order Level A

    Focus moves to the first field on open and back to the trigger on close, so the reading order follows what is on screen.

  • 2.4.7 Focus Visible Level AA

    Every control shows a visible ring when it has keyboard focus.

  • 4.1.2 Name, Role, Value Level A

    role=dialog and aria-modal come from the dialog element; the trigger says it opens a dialog.

  • 4.1.3 Status Messages Level AA

    Saving is confirmed in a status message that screen readers announce without moving focus.

Usage

When to use it

Use it

  • A short task that has to be finished or dismissed before going on, like editing one record or confirming a choice.
  • Content that needs the full attention of the person, briefly.

Use something else

  • Long forms or anything people need to look things up for: give it a page.
  • Messages that need no answer: use a toast or a status message.
  • Information to consult while working on the page: use a non-modal dialog or a drawer.

Common failures

How it usually goes wrong

  • A div styled to look like a dialog

    Without the dialog element or role=dialog plus aria-modal, a screen reader can wander out into the page behind, which it cannot see.

  • Focus left on the page behind

    If focus does not move into the dialog, keyboard users keep tabbing through hidden content, and screen reader users never hear it opened.

  • Focus lost on close

    When the dialog closes and focus goes to the top of the page, people have to find their way back. Return it to the trigger.

  • No way out

    A dialog that ignores Escape and has no visible close button traps everyone who cannot click outside it.

  • An unnamed dialog

    "Dialog" on its own tells nobody what it is for. aria-labelledby pointing at the heading names it.

  • The page scrolls underneath

    Scrolling the page behind a modal is disorienting on touch screens. The page is locked while the dialog is open.

Notes

Building it

  • Open it with showModal(), not show(): only the modal version makes the rest of the page inert and puts the dialog in the top layer.
  • The autofocus attribute on the first field decides where focus lands. For a destructive confirmation, put it on the safest button instead.
  • Browsers return focus to the previously focused element when a modal dialog closes; the script does it as well for older engines.
  • The close-on-outside-click option only adds a way to close; Escape and the visible buttons still work, so nobody depends on a pointer.

Sources: WAI-ARIA Authoring Practices: Dialog (Modal) · HTML: the dialog element

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction