Modal Dialog

A modal is a dialog that appears on top of the main content and disables interaction with the rest of the page. Modals are used to display important information or to prompt the user to take an action.

A modal dialog may be implemented using the semantic HTML dialog element or a non-semantic element with a role of dialog or alertdialog and with the aria-modal attribute set to true.

The modal launcher should be a button widget. For more information see the Button component.

Role

The modal must have a role of dialog or alertdialog. The best way to achieve this is to use the dialog element. Alternatively you can use a div element and add the role="dialog" or role="alertdialog" attribute to it.

1<!-- Many modern browsers support the dialog element -->
2<dialog>
3 {content}
4</dialog>
5
6<!-- Role attribute can also be used -->
7<div role="dialog">
8 {content}
9</div>

Accessibility label

Modal dialogs should be labeled so all users can perceive their context and purpose. If the modal has a launcher button, the best practice is to use aria-labelledby="launcher-id" to provide the accessible name. If the modal does not have a launcher button, the best practice is to use aria-label="Modal Title" to provide the accessible name.

1<!-- Modal has a launcher button -->
2<button id="launcher-id">Find zip code</button>
3<dialog aria-labelledby="launcher-id">
4 {content}
5</dialog>
6
7<!-- Modal does not have a launcher button -->
8<dialog aria-label="Find zip code">
9 {content}
10</dialog>

Perceivable

The modal must be visible when it is opened. On a dialog element, this can be achieved by adding the open attribute, or by calling the showModal method. When using a div element, visibility can be controlled using the display or visibility CSS properties or by using conditional rendering.

It must be possible to close the modal using the escape key. It also recommended for the modal to have a visible, keyboard accessible, properly labeled close button.
1 function openModal() {
2 const modalElement = document.getElementById('my-modal');
3 modalElement.classList.add('open');
4 ...
5 modalElement.addEventListener('keydown', (event) => {
6 if (event.key === 'Escape') {
7 closeModal();
8 }
9 });
10 }
When the modal dialog is visible, it must be visible to screen readers. This will usually be the case unless the modal dialog element is hidden using the `aria-hidden` attribute.

When the modal dialog is not visible, it must be hidden from screen readers. Good techniques for making the modal dialog invisible (i.e. adding style attributes e.g display: none or visibility: hidden, using the hidden attribute, or using conditional rendering) will also hide the content from screen readers and ensure the modal dialog is inert and not focusable.

If other methods are used to make the modal dialog invisible such as setting the opacity to 0, then the modal dialog must be explicitly hidden from the screen reader using the aria-hidden="true" attribute. The modal dialog and its descendants must also be excluded from the focus sequence either by setting the inert attribute on the modal dialog or by setting the tabindex="-1" on any potentially focusable descendants.

Focus management

When a modal dialog is opened, focus must be moved to the dialog. Generally focus should be moved to the first focusable element in the dialog. In some cases, such has when the dialog contains a lot of content, it may be better to move focus to a non-focusable element at the beginning of the content or to the dialog itself.

In a react modal, this could be done using the useEffect() method

1useEffect(() => {
2 if (this.modalRef.current) {
3 // Get tabbable elements using tabbable library
4 const tabbableElements = tabbable(this.modalRef.current);
5 if (tabbableElements.length) {
6 tabbableElements[0].focus();
7 } else {
8 this.modalRef.current.focus();
9 }
10 }
11});

An example of doing this in a native javascript implementation

1 function openModal() {
2 const modalElement = document.getElementById('my-modal');
3 ...
4 const tabbableElements = tabbable(modalElement);
5 if (tabbableElements.length) {
6 tabbableElements[0].focus();
7 } else {
8 modalElement.focus();
9 }
10 }

When a modal is closed, focus should be returned to the launcher button.

1 function closeModal() {
2 ...
3 launcher.focus();
4 }

ARIA owns

If the modal should contain elements that cannot be implemented as descendants of the modal, the aria-owns attribute can be used to indicate that the modal owns the elements. However, since the aria-owns attribute is not supported by some assistive technologies, it is best practice to avoid it. If it must be used, the aria-owns attribute should be set on the modal and should point to the IDs of the owned elements.

ARIA modal

The aria-modal attribute must be set to true on the modal element unless it is a semantic dialog element. This tells screen readers that the dialog is a modal and that the user cannot interact with the rest of the page.

1<!-- Dialog element automatically gets aria-modal="true" -->
2<dialog>
3 {content}
4</dialog>
5
6<!-- aria-modal="true" must be added to div element -->
7<div role="dialog" aria-modal="true">
8 {content}
9</div>

Dimmed background

When a modal dialog is opened, the rest of the page should be visually dimmed and hidden from screen readers. Using a semantic dialog element would natively dim the page. Also, some screen readers will automatically hide the background if aria-modal="true" is used. Otherwise, the inert or aria-hidden attributes should be applied to all branches of the DOM tree that are not part of the modal. This prevents dimmed content being conveyed to screen reader users while the modal dialog is open.

Using react useEffect() method

1 useEffect(() => {
2 if (this.modalRef.current) {
3 ...
4 const rootNode = document.getElementById('root');
5 rootNode.setAttribute('inert', '');
6 }
7 });

Using native javascript

1 function openModal() {
2 ...
3 const rootNode = document.getElementById('root');
4 rootNode.setAttribute('inert', '');
5 }

Focus trap

When a modal dialog is opened, focus must be trapped within the dialog. This means that the user cannot tab out of the dialog. One way to achieve this is to use the focus-trap library. Similar libraries are available for common UI frameworks.

1 // Using focus-trap library
2 function openModal() {
3 const modalElement = document.getElementById('my-modal');
4 ...
5 focusTrap.createFocusTrap(modalElement).activate();
6 }

Summary of Tests

NameConformance LevelWCAG Success Criteria
RoleA4.1.2 Name, Role, Value
Accessibility labelA4.1.2 Name, Role, Value
Modal is visibleA2.1.1 Keyboard
Screen readableA1.3.2 Meaningful Sequence
Focus managementA2.1.1 Keyboard, 2.4.3 Focus Order
ARIA ownsA1.3.1 Info and Relationships
ARIA modalA4.1.2 Name, Role, Value
Dimmed backgroundA1.3.2 Meaningful Sequence
Focus trapA2.1.1 Keyboard, 2.4.3 Focus Order
Escape keyA2.1.1 Keyboard