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
.
Modal launcher
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>56<!-- 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>67<!-- 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.
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.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 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 library4 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>56<!-- 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 library2 function openModal() {3 const modalElement = document.getElementById('my-modal');4 ...5 focusTrap.createFocusTrap(modalElement).activate();6 }