Accessible modal dialogs
An accessible modal dialog moves focus into itself when it opens, keeps keyboard focus inside while it is open, closes with Escape and a visible button, has a name, and returns focus to the control that opened it. The HTML dialog element opened with showModal() does most of this for you; you still supply the name, a sensible first focus and the close button.
Published . Last reviewed .
A modal dialog is a window laid over the page that blocks everything behind it until the user responds. That blocking is why it needs care. A keyboard user or a screen reader user has to be taken into the window, kept inside it, given a clear way out and put back where they were. W3C's ARIA Authoring Practices Guide (APG) pattern for a modal dialog describes that behavior, and the HTML dialog element, opened with showModal(), supplies much of it for you.
Dialogs are one piece of the wider work of an accessible site; how to make a website accessible covers the rest.
What a modal dialog has to do
The APG describes four behaviors:
- When the dialog opens, focus moves to an element inside it.
- Tab moves to the next tabbable element in the dialog and, from the last, wraps to the first. Shift+Tab goes the other way.
- Escape closes the dialog.
- When it closes, focus normally returns to the element that opened it, or to another fitting element if that one no longer exists.
The APG adds that content behind an open modal dialog is inert (users cannot interact with it), that the container has the dialog role and a name, and that it is strongly recommended that every dialog's tab sequence include a visible button, such as a close icon or Cancel. WCAG has no criterion called "modal dialog"; the behavior falls under several criteria, and these four are Level A:
| Criterion | Level | What it means for a modal dialog |
|---|---|---|
| 2.4.3 Focus Order | A | W3C's Understanding page says focus is set within a modal dialog on opening, outside content is inert, and focus returns to the opening button, or the element after it, on dismissal. |
| 2.1.2 No Keyboard Trap | A | Restricting focus to a modal dialog is acceptable when users know how to leave it. |
| 4.1.2 Name, Role, Value | A | The close button and every other control in the dialog need a name and role that software can determine. W3C says custom controls need additional measures. |
| 1.3.1 Info and Relationships | A | Our reading: a visible title that tells sighted users what the dialog is should be tied to it in the code, which is what aria-labelledby does. |
Start with the native dialog element
W3C's technique H102, creating modal dialogs with the HTML dialog element, is a sufficient technique for 2.4.3 when a page changes dynamically. It lists the advantages of the native element, where the browser moves focus into the dialog, returns it to the opener on close if the opener still exists, keeps focus within the dialog and the browser's own interface, makes outside content inert and closes the dialog on Escape. MDN's dialog page confirms the inert and Escape behavior for showModal(), and advises against toggling the open attribute, which gives a non-modal dialog. A minimal version:
<button type="button" id="open-shipping">Shipping details</button>
<dialog id="shipping-dialog" aria-labelledby="shipping-title">
<h2 id="shipping-title">Shipping details</h2>
<p>Delivery times are shown at checkout.</p>
<form method="dialog">
<button autofocus>Close</button>
</form>
</dialog>
document.querySelector("#open-shipping").addEventListener("click", () => {
document.querySelector("#shipping-dialog").showModal();
});
The opener is a real button, so it works from the keyboard. aria-labelledby points at the visible title, which names the dialog. The Close button sits in a form with method="dialog", which closes the dialog without custom JavaScript (H102 uses the same pattern), and autofocus on it follows MDN's suggestion for when nothing else needs more immediate interaction.
What the browser does and what is still yours
| Behavior | The browser does it | You still do |
|---|---|---|
| Focus moves in | Yes, to the autofocus element, otherwise the first focusable one (MDN). | Choose the right first element. |
| The page behind is inert | Yes (MDN). | Dim it with ::backdrop so sighted users see it is inactive. |
| Escape closes it | Yes, by default (MDN). | Never block it, and add a visible close button. |
| Exposed as modal | Yes, as if aria-modal="true" were set (MDN). | Nothing. |
| Focus returns to the opener | Yes, per the HTML Standard and H102. | Test it, and place focus sensibly if the opener has gone. |
| A name | No. | Point aria-labelledby at the visible title. |
| A visible close button | No. | Add one. MDN calls it the most robust way for everyone to close a dialog. |
| Opening it as modal | Only when you call showModal(). | Do not use open or show() for a modal. |
One row deserves a test. The HTML Standard says a dialog records the focused element before it is shown and, when a modal dialog closes, runs the focusing steps for that element. H102's test procedure also checks that focus returns to the trigger, so check it in the browsers you support.
Choose where focus lands
MDN says it is important to consider the most appropriate place to set focus, and that autofocus on the right element helps ensure it happens. The APG gives the cases:
- Most dialogs: the first focusable element, which is the browser's default.
- Lists, tables, several paragraphs or long content: a static element at the top, such as the title or first paragraph, given
tabindex="-1"and focused on opening, so the start of the content is in view. For lists, tables or several paragraphs, the APG also advises leaving outaria-describedby, since that content reads badly as one unbroken string. - Final, hard-to-reverse actions, such as deleting data or completing a transaction: the least destructive action, such as Cancel.
- Dialogs that only inform or continue processing: the most frequently used element, such as OK or Continue.
H102's example takes the heading route: the dialog's heading has tabindex="-1" and receives initial focus, and the close button comes last in the source, so it does not take focus first even though it appears at the top. Do not put tabindex on the dialog element itself, which both MDN and the HTML Standard rule out.
Name the dialog and label what is inside
A native dialog has the dialog role built in, according to MDN, but it still needs a name. Point aria-labelledby at the visible title, or use aria-label where there is no visible title (APG). aria-describedby is optional and suits only simple content, such as one sentence that states the dialog's purpose.
Inside the dialog the usual rules apply. Every form field needs a label (see accessible forms). An icon-only close button needs a name, such as aria-label="Close", because 4.1.2 requires a programmatically determinable name for all user interface components. Use a button for it, since it acts on the page rather than taking you somewhere (see buttons vs links).
Closing the dialog
MDN lists the ways a dialog can close: a form with method="dialog", close(), Esc and, where enabled, a click outside it. It calls an explicit close, confirm or cancel button the most robust way for all users to close the dialog, and says keyboard users expect Esc to work, so that behavior has to be implemented and maintained. Treat a click outside as a convenience, never the only way out. MDN also describes a closedby attribute for choosing which actions close a dialog; check its compatibility table before relying on it.
Then focus has to land somewhere sensible. W3C's failure F85 gives two examples of what goes wrong: a dialog placed at the end of the page is shown without moving focus, so the user must tab through the rest of the page to reach it, and a menu closes and sets focus to the document, so the user must tab from the start of the page to get back. Its test procedure applies the same closing check to dialogs: focus should be back on the trigger. If the opener has gone, for instance because the dialog deleted the item, move focus to a logical nearby element; both the APG and F85 accept that.
Is a dialog that holds focus a keyboard trap?
Not when people know how to leave. W3C's Understanding page for 2.1.2 No Keyboard Trap says restricting focus is acceptable in a modal dialog as long as users know how to exit, and its own modal dialog example closes with Cancel, OK or Esc. It adds that Esc is commonly used on physical keyboards, though that depends on the platform. So give both: Esc and a visible close or cancel button. Our reading: a dialog with no working Esc and no visible close or cancel button is a keyboard trap. The keyboard accessibility test covers the other places to look.
Building a modal without the dialog element
If the native element does not fit, every behavior in the table above is yours. The requirements, from the APG, MDN and W3C:
- Role and name. Give the container
role="dialog", make every element needed to operate the dialog a descendant of it, and name it witharia-labelledby. - aria-modal, carefully. The APG says to set
aria-modal="true"only when your code prevents all interaction with content outside the dialog and visual styling obscures that content. The reason, per the APG: marking a dialog modal can stop users of some assistive technologies from perceiving content outside it, so a dialog marked modal that does not actually block the page causes those users severe problems. - Make the rest of the page inert. MDN says elements with the
inertattribute cannot be focused, are removed from the tab order and are excluded from the accessibility tree, and lists it as Baseline widely available. Put it on the containers that hold the rest of the page, never on an ancestor of the dialog (MDN says only a modal dialog opened withshowModal()escapes inertness inherited from an ancestor), and dim the page, because MDN notes inert content has no default visual cue. - Be careful with aria-hidden. Setting
aria-hidden="true"on the rest of the page is the older approach, which the APG treats as legacy. It hides content from assistive technology but does not by itself stop keyboard focus: W3C's accessibility conformance testing (ACT) rule on aria-hidden notes that a focusable element inside can still be reached by sequential focus navigation. See aria-hidden and focus. - Focus. Move focus in on opening, wrap Tab and Shift+Tab inside the dialog, close on Escape and return focus on closing. Skipping the first step risks the F85 failure above.
Most of this is already done by showModal(), and H102 presents the native element as the lower-effort route. Other widgets that open on demand share these focus rules; see accessible menus and ARIA basics.
Confirmation dialogs and non-modal dialogs
A confirmation, such as "Delete this file?", is a modal dialog that interrupts the user to get a response. The APG gives it its own role, alertdialog, which lets assistive technologies and browsers tell it apart from other dialogs, and calls for a name and an aria-describedby that points at the message. MDN lists alertdialog as a permitted role on the native dialog element, so a confirmation can use showModal() too. For an action that is hard to reverse, start focus on the least destructive button.
A non-modal dialog, opened with show(), leaves the rest of the page usable, and MDN says it does not close with Esc by default. Use one when people may need the page while it is open, such as a help panel. The 2.4.3 Understanding page's example for non-modal dialogs puts the dialog's interactive elements in the focus order immediately after the button that opened it.
Test a modal dialog with the keyboard
H102 gives a test procedure. This version adds the Tab cycle and Escape checks from the APG and a screen reader check:
- Open the dialog from the keyboard, with Enter or Space on its trigger.
- Check that focus moved into the dialog and that you can see where it is; see focus indicators if you cannot.
- Press Tab and Shift+Tab several times. Focus should stay in the dialog, although browser controls may join the cycle, and the page behind should never take focus.
- Press Escape. The dialog should close.
- Check that focus is back on the trigger. Open the dialog again, close it with the close button and repeat the check.
- With a screen reader running, open the dialog and listen for its name. Announcements can differ between screen readers and browsers, so repeat the check in more than one; how screen readers read a page covers the basics.
Check it yourself
The steps above need a person with a keyboard. Two free checks cover the markup around them. The accessibility bookmarklet reads the page as your browser has rendered it, including content added by JavaScript, and reports buttons with no accessible name, form fields with no label and low-contrast text, all of which can occur inside a dialog's content. It cannot tell whether a page works with a keyboard, and its limits list notes that a site with a modal dialog open can cover its panel, so use the keyboard steps to test the dialog itself.
Scan my site for free runs rule-based checks on the HTML your server sends and flags a button with no accessible name and aria-hidden on a focusable element. A dialog that a script builds after the page loads is not in that HTML, and a scan never presses a key. What we check lists every rule.
Modal dialog questions
Should I use the HTML dialog element or build my own modal?
Use the native element when you can. W3C's technique H102 lists its advantages: the browser moves focus in, returns it on close, keeps focus inside, makes the page behind inert and closes on Escape. You still add a name, a close button and a sensible first focus. Build your own only when you must, and then supply every behavior yourself.
Where should focus go when a modal dialog opens?
Generally on the first focusable element inside it, which is the browser's default for showModal(). The ARIA Authoring Practices Guide suggests a static element at the top with tabindex of -1 for long or structured content, the least destructive button for hard-to-reverse actions, and the most used button, such as OK, for simple information dialogs.
Do I need aria-modal on a dialog element?
Not for the native element: MDN says a dialog opened with showModal() implicitly has aria-modal set to true. For a custom dialog, the ARIA Authoring Practices Guide says to set it only when your code blocks all interaction with content outside the dialog and visual styling obscures that content.
Should the Escape key close a modal dialog?
Yes. MDN says keyboard users expect Escape to close modal dialogs, and a dialog opened with showModal() closes on Escape by default. Also provide a visible close or cancel button, which MDN calls the most robust way to let every user close the dialog, and which gives people a clear way out of the focus cycle.
What is the difference between a modal and a non-modal dialog?
A modal dialog blocks the rest of the page, which becomes inert, until it is closed. A non-modal dialog lets people keep using the page. In HTML, showModal() opens a modal dialog and show() a non-modal one, and MDN says a non-modal dialog does not close with Escape by default.
How do I make a confirmation dialog accessible?
Treat it as a modal dialog with the alertdialog role. The ARIA Authoring Practices Guide calls for a name, an aria-describedby that points at the message, and aria-modal only when the page behind is truly blocked. For an action that is hard to reverse, start focus on the least destructive button, such as Cancel.
Run the automated part first
The free scan runs 16 rule-based checks on your page's HTML, including buttons with no accessible name and aria-hidden on focusable elements. It cannot open a dialog or press a key, so use the test above for the rest. No signup.
Scan my site for freeRelated guides
- How to make a website accessible: the 10-step plan these guides build on.
- Accessible navigation menus: menus and mega menus that work by keyboard.
- Accessible carousels: controls, pausing and hidden slides.
- Accordions, tabs and disclosures: the patterns and the native details element.
- ARIA basics: the first rule of ARIA and when not to use it.
- aria-hidden and focusable elements: a control the keyboard reaches but a screen reader cannot.
- Touch target size: the 24 by 24 pixel rule in WCAG 2.2.
- CAPTCHA accessibility: why most CAPTCHAs exclude people, and the alternatives.
- Accessible forms: labels, instructions and errors.
- Form error messages: identifying errors and suggesting fixes.
- Buttons vs links: which to use and why it matters.