See the live demo and test pages.
InstallYou can get this package on npm:
$ npm i aria-modal-dialog
$ git clone https://github.com/scottaohara/accessible_modal_window.git
Or download a zip of the repository.
The CSS for this component is included in
assets/css/. The classes are added to the base markup when the script/page loads. The base CSS has only the most necessary styling to visually convey it as a modal dialog. You will need to modify the CSS to integrate the dialog into your project's visual aesthetic.
Standard UsageInclude the a11y.modal.js file at the bottom of your document, or concatenated into your primary .js file, as part of your build process.
Ideally modal dialogs are activated by a purposeful user interaction with a
button, or an element that has been modified to have the semantics and keyboard functionality of a
button. While this script does allow dialogs to be activated by other means, a dialog opening without a purposeful action can be a confusing and frustrating user experience to many.
Modal Dialog TriggerThe baseline for a
buttonto open a modal dialog should consist of the following:
data-modal-openattribute is used to point to the specific modal this
buttonis meant to activate. Adding a
buttonshould do something, but is currently inactive.
aelement may be used as the dialog trigger, and progressively enhanced to be a
button. This will allow the
ato act as an in-page link to the would-be dialog's contents.
<a href="#unique_ID_2" class="_your_class(es)_here_" data-modal-open> Launch My Modal </a>
hrefattribute of a link may act as the identifier for the target dialog, if the
hrefto the appropriate URL, and set the
IDREFof the dialog within the document.
Modal Dialog Base MarkupThe minimum markup for a modal dialog would be the following:
<div id="unique_ID_to_match_data-modal-open" data-modal> <h1> A descriptive title for the dialog </h1> <div> <!-- primary content of the dialog here --> </div> </div>
If you need to support NVDA prior to 2017.4, then consider the following minimum markup pattern, which will add a
divthat wraps the contents of the modal dialog. This will correctly allow users to navigate the contents of a dialog with NVDA's virtual cursor, and not automatically enter users into forms/application mode:
<div id="unique_ID_to_match_data-modal-open" data-modal> <div data-modal-document> <h1> A descriptive title for the dialog </h1> <div> <!-- primary content of the dialog here --> </div> </div> </div>
Lack of features are not bugsThis script does not presently utilize the
aria-haspopup="dialog"on a dialog's trigger(s). Nor does it use
aria-modalon the element with
role="dialog". The code to add these attributes are currently in the dialog script, but commented out until they receive full non-breaking support (see Screen Reader quirks).
Update August 15, 2018: Testing with Safari Technology Preview (Release 63 (Safari 12.1, WebKit 13607.1.2.1)) on macOS 10.13.6, it appears that
aria-modal="true"will begin to work properly with VoiceOver. This is great news for the future, but until a good percentage of people update from Safari 11.x to 12,
aria-modalshould continued to be used with caution.
Inert PolyfillFor this script to provide peak accessibility, it must also utilize the
inertpolyfill from Google. While the dialogs have a function to keep focus within the dialog, looping through any focusable elements within itself, the inert polyfill will help prevent a user from accessing the browser's chrome (e.g. the address bar) and then being able to navigate back into the obscured document. The dialog script doubles down on the elements with
inert="true"and also add an
aria-hidden="true"as well. This ensures that not only can users not access elements within the obscured document by keyboard navigation, but that these elements will not be revealed in screen reader listings of elements within a document (e.g. listings of landmarks/regions, headings or form controls with NVDA and JAWS, or be revealed in VoiceOver's rotor menus.)
Configuration attributesThe following attributes are used to setup instances of the dialog triggers (buttons), the dialog container, and any necessary child elements of the dialogs.
data-modal-open: If used on a
buttonor element with
role=button, this attribute must contain the
dialogit is to activate. If used on an
aelement with an
hrefpointing to the
dialogit is to activate, then this attribute can be set to the empty string.
data-modal-auto: Indicates that the trigger's associated modal dialog should activate on page load.
data-modal-auto-persist: Indicates that the trigger's associated modal dialog should continue to activate on page reload. Without this attribute, the associated modal dialog will only auto activate once.
data-modal: The primary hook for indicating the element is meant to be transformed into a modal dialog. Leaving the attribute's value empty will result in a standard
role="dialog". Setting the value to be "alert" will indicate that the script should add
role="alertdialog"to the element.
data-modal-hide-heading: This attribute will find set a class of
at-onlyto the first heading of the modal dialog.
data-modal-close: Adding a value to this attribute will change the auto generated close button from an "icon" button to an inline button with a visible label of the value.
data-modal-close-class: The value set to this attribute will be become a class name added to generated close button.
data-modal-focus-close: This attribute serves as a hook to autofocus the generated close button when the dialog is opened.
data-modal-manual-close: If you want full control over where your close button goes, adding this attribute to the dialog container will stop the generation of a close button. It's up to you to hard code the close button yourself.
data-modal-auto: Indicates this modal dialog should activate on page load.
data-modal-auto-persist: Indicates that this modal dialog should continue to activate on page reload. Without this attribute, the modal dialog will only auto activate once.
Dialog Children Attributes
autofocus: If a dialog has this attribute on a child element, move focus to this element instead of the dialog container.
data-autofocus: Since it is invalid to have multiple
data-modal-description: This attribute will be used to auto-generate a unique ID, and become the pointer for the generated
aria-describedbythat is added to the dialog container.
data-modal-close-btn: Add this attribute to
buttonelements within the dialog that should be allowed to close the dialog.
Screen Reader QuirksThings of note for why certain decisions were made, and how different screen reader and browser combos have their own inconsistencies in announcing modal dialogs.
- Do not set dialogs to
display: noneby default.
display: none;by default, even when set to
display: block;VO will not move focus to the element, even if focus is programmatically set. To get around this, the CSS for these dialogs use
visibility: hidden;for the inactive state, and
visibility: visible;for showing the dialog.
visibility: hiddendoes not remove an element from the document's flow, it's important that
position: absolute/fixedis set to the dialog, regardless of whether it's active or not.
- The first element of a modal dialog should be its heading (which provides its accessible name).
- Note that NVDA will not announce the dialog role when focus is set to the dialog element itself. For instance, in NVDA + IE11, it will simply announce the accessible name of the dialog, and nothing more. In more standard browser pairings like Firefox or Chrome, the accessible name of the dialog will be announced, and then the contents of the dialog will begin to be announced, without ever mentioning the dialog role.
More information about modal dialogsArticles I've written about modal dialog accessibility.
Making modal windows better for everyone - Smashing Magazine (2014)
Accessible Modals: Revisited (version 2 release article) (2016)
The state of modal dialog acccessibility (2018)
License & SuchThis script was written by Scott O'Hara.
It has an MIT license.
Do with it what you will :)