Skip to content
Pandus

Accessible modal dialog

Updated October 14, 2026 · 6 min read

Newsletter pop-ups, quick view, size guides, "added to cart" overlays, login forms, cookie preferences — online shops are full of modal dialogs, and they are one of the most common reasons keyboard and screen reader users get stuck. This page shows the correct pattern. It is part of the accessible UI patterns series.

When to use a modal

The APG defines a dialog as "a window overlaid on either the primary window or another dialog window", and says: "windows under a modal dialog are inert" — users cannot interact with the page until the dialog closes. Use a modal when the user must finish or dismiss a task before going on: confirm deletion, log in, choose a variant in a quick view. For information that does not block the task (an "Added to cart" message), a non-modal message or a status message is usually less disruptive.

Relevant WCAG criteria

Code example

<button type="button" id="open-size-guide">Size guide</button>

<dialog id="size-guide" aria-labelledby="size-guide-title">
  <h2 id="size-guide-title">Size guide</h2>
  <table>…</table>
  <button type="button" id="close-size-guide">Close</button>
</dialog>

<script>
  const dialog = document.getElementById('size-guide');
  document.getElementById('open-size-guide')
    .addEventListener('click', () => dialog.showModal());
  document.getElementById('close-size-guide')
    .addEventListener('click', () => dialog.close());
</script>

What the native element does, according to the HTML standard:

  • showModal() places the dialog in the top layer, blocks the document so the rest becomes inert, remembers the previously focused element and runs the dialog focusing steps, which honour the autofocus attribute.
  • A close request (for example Escape) fires a cancelable cancel event and then closes the dialog. The closedby attribute controls whether clicks outside also close it.
  • close() returns focus to the previously focused element when the dialog was modal.

If you build the dialog yourself with ARIA, follow the APG: the container has role="dialog", aria-modal="true" and aria-labelledby pointing to the visible title; aria-describedby is optional and best left out when the content contains lists or tables. You then have to make the background inert, trap Tab inside the dialog, handle Escape and restore focus yourself.

Keyboard interaction

From the APG dialog (modal) pattern:

Key Function
Tab Next tabbable element inside the dialog; from the last one, back to the first
Shift + Tab Previous tabbable element inside the dialog; from the first one, to the last
Escape Closes the dialog

Focus on open: "focus moves to an element contained in the dialog", generally the first focusable element. Focus on close: it "returns to the element that invoked the dialog", unless that element no longer exists — then to another element that keeps a logical workflow. After "Remove item", for example, focus can go to the cart heading.

Common mistakes on online shops

  • Newsletter pop-up on page load that steals focus, has no name and no keyboard-reachable close button.
  • Close "×" without an accessible name, or a close icon that is a div.
  • Focus stays behind the overlay: Tab moves through the page under the dark backdrop.
  • No Escape and no visible way to close on mobile once the dialog is longer than the screen.
  • Focus lost after closing, jumping to the top of the page instead of the "Quick view" button.
  • Quick view dialogs that rebuild the product page with duplicate IDs and nested forms.
  • Cookie banners that are neither modal nor out of the way and cover the focused element at the bottom of the screen — a 2.4.11 failure according to W3C.

What automated checks catch vs manual testing

Automated (axe-core rule) What it finds
aria-dialog-name role="dialog" or alertdialog without a name
button-name Close buttons without a name
aria-hidden-focus Focusable elements inside content hidden with aria-hidden="true"
aria-hidden-body aria-hidden="true" on the body element
aria-valid-attr-value aria-labelledby pointing to a missing ID
color-contrast, target-size Low contrast and small targets inside an open dialog

All are cards in our rule library. The weekly scan sees the page as it loads: a dialog that is closed is not tested, an auto-opening pop-up is. With the browser extension you can open the dialog and then run the check. Manual testing: open the dialog with the keyboard, Tab through it to the end and back, press Escape, check where focus lands, zoom to 400% and listen to how a screen reader announces it. The steps are in how to test WCAG.

Run the free one-page check. It is an automated check, not a full audit.

Frequently asked questions

What makes a modal dialog accessible?

Focus moves into the dialog when it opens, Tab and Shift + Tab stay inside it, Escape closes it, focus returns to the button that opened it, the content behind is inert, and the dialog has a name from its visible title.

Should I use the HTML dialog element?

For a modal, the dialog element opened with showModal() does much of the work: the HTML standard puts it in the top layer, makes the rest of the document inert, handles initial focus and closes it on a close request such as Escape. Test it with the browsers and screen readers your customers use.

Where should focus go when a dialog opens?

The APG says focus moves to an element inside the dialog, generally the first focusable element. For a long text it can be better to focus the start of the content. With the dialog element you choose it with the autofocus attribute.

Only if it is built as one. A non-modal banner must not hide focused elements: W3C says a sticky cookie banner fails 2.4.11 Focus Not Obscured if it entirely covers a component that receives focus.

Can an automated check test a modal?

Partly. It finds dialogs without a name, close buttons without a name and focusable elements hidden with aria-hidden. It cannot test focus movement, the focus trap or Escape, and a closed dialog is not checked at all.

Sources

  • W3C WAI, APG Dialog (Modal) pattern: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
  • WHATWG, HTML Living Standard, the dialog element: https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element
  • W3C, Understanding 2.4.11 Focus Not Obscured (Minimum): https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html