The current state of modal dialog accessibility

Posted on Friday, 29 June 2018 by Scott O'Hara

Modal dialogs continue to be troublesome UI components all across the web. Putting aside the fact they are often misused and thrust on users in a manner that interrupts their current task (asking me to sign up for your newsletter, while I’m in the middle of reading an article, is not cool), even appropriately used modals often lack considerations for accessibility.

While poor modal dialog UX is still common, that’s not to say there haven’t been strides to make these experiences better. Performing a search for “accessible modal dialog”, you’ll find many developers (myself included) have been writing about and releasing production-ready custom dialog scripts, and awareness of the dialog element has been growing. But to truly get a handle on the current state of modal dialog accessibility, one should be aware of the current issues facing both native and custom (ARIA) modal dialogs.

The native dialog element, what’s the hold up?

I mentioned the dialog element in an article I wrote for Smashing Magazine (2014), as well as in my follow-up post about my modal dialog script (2016). At the time of writing those articles, dialog had been at least partially implemented into Chrome and Firefox behind a flag.

It’s been almost 4 years now (5ish if you go back to the earliest implementation of the dialog element in Chrome Canary), but adoption of the element is still lacking.

As for a quick rundown of the different browsers:

  • Chrome 37+ and other blink-based browsers support the dialog element (for instance: Opera 24+, Opera Mobile 46, Android/Chrome Android 67).
  • Firefox (53+) still requires the dialog to be manually enabled.
  • Safari (macOs and iOS) do not presently support dialog and have a request for inclusion dating back to 2012
  • Microsoft Edge has it marked as “under consideration”.
  • Finally, Internet Explorer, will never support dialog.

While browser implementation hasn’t budged much in the last few years, there has been some interesting progress with ARIA and additional methods to create better experiences for custom modal dialogs.

A brief recap of expected modal dialog behavior

Two modal dialogs talking to each other. The first exclaiming ‘your document is showing…’, where an icon of a document is only partially obscured by the second dialog. The second dialog looks embarrassed.

But before getting into some of the newer bits, let’s do a quick recap on what should be expected for an accessible modal dialog.

  1. When a modal dialog is activated, focus must be moved to the dialog. Where focus is initially placed may vary depending on the dialog’s content, but focusing the dialog itself can provide a consistently predictable user experience.
  2. A modal dialog should have an accessible name, announce itself as a dialog, and should provide standard methods for the user to close it. e.g. by a close button, by use of esc key, mouse clicking or tapping outside of the dialog, and ensuring F6 will continue to allow the user to move keyboard focus to the browser’s address bar.
  3. While the modal dialog is active, the contents obscured by the modal dialog should be inaccessible to all users. This means that the TAB key, and a screen reader’s virtual cursor (arrow keys) should not be allowed to leave the modal dialog and traverse the content outside of the dialog.
  4. When a modal dialog is closed, focus should return to the control that initially activated the dialog. This will allow keyboard and screen reader users to continue to parse the document from where they left off. If a modal dialog was not initiated by a purposeful user action (boo), or the element that had activated the dialog is no longer in the DOM, then closing the dialog should place the user’s focus in a logical location. e.g. if a dialog was opened on page load, then focus could be placed on either the body or main element. If the trigger was removed from the DOM, then placing focus as close to the trigger’s DOM location would be ideal.

OK, now that that’s squared away, let’s get back to the new hotness…

Updates to ARIA and using inert

Since the release of my previous script, the aria-modal attribute has been introduced, and allowing “dialog” as a value for aria-haspopup have been added to ARIA 1.1. These additions would be a huge help with making modal dialogs more accessible, if not for the following implementation bumps:

aria-modal

aria-modal is meant to indicate to screen readers that only content contained within a dialog with aria-modal="true" should be accessible to the user.

This attribute is a pretty big deal and a very welcome addition to the specification. The aria-modal attribute will help in taking care of one of the biggest hurdles with custom modal dialogs; keeping a screen reader within the active dialog, as creating a JavaScript trap for standard keyboard focus is not enough to curtail a wandering virtual cursor.

Testing across different screen reader and browser pairings, this attribute largely performs as expected when the value is set to true, with one unfortunate exception. Safari + VoiceOver on both macOS and iOS have issues with it making static content within a modal dialog inaccessible (see logged WebKit bug).

Additionally, aria-modal="false" does not work as expected in some browser and screen reader pairings. This issue is far less severe as one could simply just not use aria-modal="false", as being set to false should convey the same information as if the attribute wasn’t present at all.

aria-haspopup

At the time of writing this, most screen readers do not yet support aria-haspopup="dialog". Often they will make no mention of a control’s association with a dialog, and some will fallback to announcing the control as opening a menu (as haspopup‘s origins were associated with menus), which would lead to quite an unexpected experience for users.

Until support for the dialog value has better implementation, it’s probably best to not use aria-haspopup on the element that opens the modal dialog. In the meantime, one could add some sort of visual and/or visually hidden indicator (icon and/or text) to inform users that a modal dialog will open.

inert paired with aria-hidden="true"

Where aria-modal still has some kinks to work out with WebKit, improved inert polyfill support (Google inert or WICG inert) can be paired with aria-hidden="true" on elements outside of a modal dialog to ensure that keyboard focus and screen reader virtual cursors will be less likely to interact with the content obscured by an active modal dialog.

For instance, a user should be able to leave the current document to access the browser’s chrome (e.g. via use of the F6 key to move focus to the browser’s address bar). When this user attempts to re-enter the document, inert and aria-hidden="true" will prevent their focus from landing on elements obscured by the modal dialog, and ensure the only place for the user’s focus to go, would be to the contents of the dialog.

Adding aria-hidden="true" to elements outside of the active modal dialog ensures that elements not within the active modal dialog will not be surfaced if a user opens a screen reader’s list of elements (headings, form controls, landmarks, etc.) in the document. This would be something that aria-modal="true" would take care of, pending its current issues with VoiceOver get resolved.

Finally, the use of aria-hidden="true" and inert together negate the ability for VoiceOver users to escape a modal dialog when reading line by line (by use of Up and Down arrow keys, without the VO modifier key). Most custom modal dialogs I’ve seen in the wild do not account for this sort navigation.

Additional Gotchas to watch out for

Beyond the above mentioned issues with aria-modal and aria-haspopup="dialog", there are a few other things I’ve uncovered in my testing of modal dialogs that should be noted:

Do not set dialogs to display: none by default.

There is an issue with iOS Safari + VoiceOver where if an element is initially set to display: none;, even when updated to display: block; VoiceOver will not move focus to the element, even if focus is programmatically set. I’ve found to get around this bug, the CSS for dialogs should instead use visibility: hidden; for their inactive state, and visibility: visible;when they are displayed. Since a dialog should be set to position: fixed; or in some situations absolute, the common side effect of visibility: hidden elements still taking up physical space in the DOM order, will be averted. Additionally useful, having a modal dialog set to visibility: hidden rather than display: none means it will be possible to utilize CSS transitions.

If using the hidden attribute to hide dialogs in their default state (which will ensure that if CSS is ever blocked, the dialogs won’t become visible – useful for a browser’s reader mode), the hidden attribute can have its CSS modified to account for the above mentioned bug:

[role="dialog"][hidden] {
  display: block;
  visibility: hidden;
}

display: block undoes the hidden attribute’s default display: none CSS, while visibility: hidden re-hides the dialog for it’s inactive state.

Overly verbose NVDA announcements

When testing with NVDA, setting focus to a typically non-focusable element (e.g. a heading with tabindex="-1") can result in NVDA redundantly announcing the focused element and the contents of the dialog multiple times.

As placing focus on the first focusable element of a modal dialog can result in inconsistent and even unfavorable starting points for a screen reader user (e.g. if the first focusable element is the close button at the end of a content heavy dialog, or an input at the mid-point of a dialog’s content where content prior to it could be missed) it’s best to just focus the dialog element itself and allow the user to navigate the dialog in sequential order as they would a standard document.

IE11 needs the first element of the modal dialog to be its heading

The first element of a modal dialog should be its heading (which provides its accessible name). This requirement is to compensate for Internet Explorer 11 + JAWS specifically. With this pairing, setting focus to the dialog element itself will announce the accessible name of the dialog, the dialog role, and then JAWS will re-announce the accessible name of the dialog, and the role of the first child element of the dialog.

For instance, if the dialog’s heading provides the accessible name for the dialog, then JAWS + IE11 will announce “heading text, dialog. heading text, heading level #”. However, if the first child is another element that does not match the accessible name of the dialog, such as a button with text “close”, it will be announced as: “heading text, dialog. heading text, button”

NVDA will not announce the dialog role when the dialog itself receives focus

Testing with NVDA, the dialog role will not be announced 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.

Wrapping up

Until native dialogs are ubiquitous across all major browsers, we’re going to continue to need ARIA to help us make sure modal dialogs are accessible. But, bear in mind, some ARIA features are also relatively new and also require some time to get full support with both browsers and screen readers.

Until either native dialogs or ARIA properties reach full support, it is our responsibility to continue to test not only our modal dialogs, but any component we are building, to ensure it provides (and continues to provide) an accessible user experience.

Browsers, screen readers, and even authoring guidelines / specifications may require changes, even breaking ones, over time. Thus it makes sense for us to look at these patterns time and time again, and make sure they still work the way they’ve been outlined and that works best for all users.

About Scott O'Hara

Scott joined The Paciello Group in 2017, bringing with him nearly two decades of experience working as a designer and front-developer for product companies and UX consulting agencies. Scott is a member of the W3C, helping to edit the HTML 5.3 specification. He enjoys working on open source projects, contributing his time to help make them more accessible.

Comments

  1. Regarding the section “Do not set dialogs to display: none by default” here’s a codePen with a very basic test case to demonstrate the issue:
    https://codepen.io/scottohara/pen/OEeBJe

    The first button will open a dialog that will not receive focus on iOS 11.4 Safari + VO. The second button, where the dialog it opens is set to visibility hidden, instead of display none, will allow focus to move to the dialog as would be expected.

  2. Nice Thomas.

    I just gave that a test on my iPhone and it seems to fix the problem most of the time. About one in every five times I tried it, VO focus still stayed on the first button and didn’t move into the dialog…but I think that’s an avenue worth exploring more since it wouldn’t require people use visibility hidden over display none, which I realize might be a uncommon choice for some devs.

    Regarding the discovery of the bug, I’d like to give credit to @backwardok (https://twitter.com/backwardok), as it was through a conversation with her that I found the CSS I was using fixed this bug.

  3. Dan, see the first item in the ARIA Authoring Practices Note for the dialog under Keyboard Interaction:

    1. When a dialog opens, focus placement depends on the nature and size of the content.
      • In all circumstances, focus moves to an element contained in the dialog.
      • Unless a condition where doing otherwise is advisable, focus is initially set on the first focusable element.

    The content in the above post under the heading Additional Gotchas to watch out for outlines conditions where doing otherwise is advisable (NVDA verbosity, IE11 are examples).

    To answer your question about getting the recommendation updated, you may file issues on the WAI-ARIA Authoring Practices at github.com/w3c/aria-practices/issues. You do not need to be a W3C member, and being one affords no preference on how issues are resolved.

  4. Adrian, Scott, the way I read what the ARIA Authoring Practices recommend is a bit different:
    – In all circumstances, focus moves to an element contained in the dialog.

    As I read it, this doesn’t allow to set focus on the dialog element itself as advised in this post (see “it’s best to just focus the dialog element itself”). While in some cases it might be beneficial, seems to me there are no exceptions allowed and focus should be set within the dialog.

    Re: the second point:
    – Unless a condition where doing otherwise is advisable, focus is initially set on the first focusable element.

    As I read it, this is about exceptions to setting focus on the first focusable element, like making a heading focusable or setting focus on an element that’s not the first focusable one. It doesn’t introduce exceptions for the previous point.

    I guess either the ARIA Authoring Practices should further clarify setting focus on the dialog element is not allowed, or this post should clarify that the advice “it’s best to just focus the dialog element itself” actually doesn’t meet what the ARIA Authoring Practices recommend.

  5. Hi Andrea,

    Thanks for your comment.

    I will clarify that pertaining to this point, I disagree with the ARIA Practices Note and instead have looked to the W3C HTML specification on where initial focus for a dialog should be set (see https://github.com/w3c/html/pull/1331 where the dialog itself should receive focus, unless an element within has an autofocus attribute).

    Setting the focus to the dialog itself ensures that users will always have a consistent starting point in a dialog. It mitigates a developer needing to determine if a dialog is too long / complex that autofocusing a form control or confirm/close button might push focus out of the visible viewport. Which is helpful in more complex dialogs that might fit nicely within a larger viewport, but would require a different focus point on smaller viewports. Working on a project where there can be multiple types of modal dialogs, it can be confusing for developers and users alike if focus placement seems inconsistent from one dialog to the next, even if those dialogs are following the ARIA Practices note exactly.

    Additionally, setting focus to the dialog itself, and letting a user navigate the contents in sequential order mitigates the need for using aria-describedby on the dialog. This will mean that a developer won’t need / forget to reference any important introductory or descriptive content that will be skipped over when a user’s focus is placed on an element after that content, in the dialog.

    Finally, when testing (demo and test pages https://scottaohara.github.io/accessible_modal_window/tests/general.html) I found that NVDA gets quite verbose when focus is set to a typically non-focusable element (like a heading) (see results of test 1b). Granted, this is more of an issue that should be solved by NVDA, but in combination with the above two points and the fact that NVDA will begin announcing all the contents of a dialog when focus is set to the dialog itself, it seems less than ideal to make a user put up with that first repetitive announcement if a better first experience could be provided.

    I hope this response provided the context you were looking for.

    Thanks again

    Scott

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.