The current state of modal dialog accessibility

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.

Like to be notified about more articles like this? Subscribe to the Knowledge Center Newsletter. It not only gives you summaries and links to our technical blog posts but also TPGi webinars, podcasts, and business blog posts – as well as accessibility and web tech conferences and other events, and a reading list of other relevant articles. You get one email a month, it’s free, requires just your email address, and we promise we won’t share that with anyone. Check the archive.
Categories: Technical

About Scott O'Hara

Scott joined TPGi in 2017 (until 2021), bringing with him nearly two decades of experience working as a designer and front-developer for product companies and UX consulting agencies.

Comments

Scott O'Hara says:

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.

Thomas Jaggi says:

Wow, thanks a lot for discovering and documenting that iOS/VO bug!

An alternative workaround would be to set the focus slightly delayed. This seems to work for me:

requestAnimationFrame(function() {
dialog.focus();
});

Demo: https://codepen.io/backflip/pen/ERBzXE

Scott O'Hara says:

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.

Nicolas says:

Yes, for the focus, I’m using a quite similar trick:

setTimeout(function() {
$button.focus();
}, 0);

Example here: https://github.com/nico3333fr/jquery-accessible-accordion-aria/blob/master/index.js#L155

It seems to fix the problem most of the time. 🙂

Dan says:

The official W3C recommendation at the moment is to send focus to the first focusable element.

https://www.w3.org/TR/wai-aria-practices-1.1/

I agree with your stance that it should pretty much always be sent to the dialogue element itself.

I see that you are a member of the W3C. Is there a way to get this recommendation updated?

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.

Andrea Fercia says:

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.

Scott O'Hara says:

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

Retrocube says:

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 an uncommon choice for some devs.

Emmanuel says:

Hey, thanks a lot for the article. One thing I’m not sure of, is what to do right now instead of using the aria-modal attribute. I ask because it seems using it is really problematic on VoiceOver and not acceptable for now.

Up until now I followed the 1.0 ARIA spec, where it’s suggested to use aria-hidden="true" for elements outside the modal. From what I understand here, this is not optimal and should be paired with inert. But this breaks VoiceOver when reading line by line.

What would be the “least bad choice” here? Using aria-hidden alone, without inert? Or is breaking this kind of navigation in VoiceOver “acceptable”? Or did you find a combination of attributes that worked ok everywhere?

Thanks.

Scott O'Hara says:

Hi Emmanuel,

I’ve paired aria-hidden and inert with positive results in my modal script: https://github.com/scottaohara/accessible_modal_window so I’d be interested in your setup where you’re finding that doesn’t work.

Emmanuel says:

Oh, by reading more carefully your article I realize I didn’t understand correctly. I thought there was a problem when navigating up/down when using VoiceOver with aria-hidden+inert. But it is actually the contrary.

Thanks.

Meharoon says:

Hi,
One doubt as you mentioned above 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.
Do you have any real time examples which buttons have visible text like “Opens a popup” or opens a dialog ?

Is it mandatory to provide “Opens a dialog” for the buttons which will trigger dialogs.

Scott O'Hara says:

Hi Meharoon,

The point of that section of the article was that aria-haspopup=”dialog” doesn’t presently have consistent support in screen readers. The function it would have provided would have been to let screen reader users know that the button would trigger a dialog (popup) to open. So the suggestion to add some sort of indicator was to mitigate the lack of support for this feature.

Whether it’s mandatory or not I think is dependent on context in which the trigger is used. If it could be considered unwanted or disruptive to a user, then it might be a good idea to let users know about the functionality of the button. If it would be expected behavior, e.g. an app where editing happens in dialogs, or if activating a button has a secondary function of loading a confirmation dialog, then it’s probably not as necessary.

Karin Carlson says:

Hi Scott, everyone.
I have a theory about perhaps the reason, or part of the reason, behind the advice to “focus on the first focusable element” and then have instructions etc. read via aria-describedby. I suspect it might be to avoid the disorientation that might be experienced when the screen reader just “reads” the page (user is not tabbing, the screen reader is just doing its thing). In that case, the screen reader is reading everything from the top down, and sooner or later will read the input and whatever labelling there is. But the focus isn’t actually moving while it’s reading. So I hear something like “clickable, name, required, invalid entry” and I think, oh, here I am at the part where I type my name. But I’m NOT there — the focus is still on the whole dialog, and I now have to tab through the dialog to get to the part where I type my name, which is the part I just listened to. Confusing!
What do you think?
I have several clients who REALLY want a definitive answer for what should be focused when a dialog opens. They both have applications, so everything is essentially a dialog. 🙂 The developer team insists that it’s easier to focus on the whole dialog rather than a component. I, like a few other people here, are a little confused about the difference in advice from you (you’re saying focus on the heading and focus on the dialog) and the W3C (focus on a focusable element). Also, I think that all dialogs should have a heading, but my dev guys like the look of only the title and no heading text. So, what’s the best thing to focus on in that case?
Thanks muchly,
Karin

Meharoon says:

So have you come across any web sites or web applications which buttons have visual text “opens a popup” or “opens a simulated dialog”

Scott O'Hara says:

Meharoon,

When I stated “…add some sort of visual and/or visually hidden indicator (icon and/or text)…” I was thinking more along the lines of a visual treatment like an icon of some sort, or maybe a variation of the button style. Visually doing something like that, along with the context in which the button is used, could indicate to users that the button has a distinct function.

The second part of that statement “visually hidden indicator” refers to the text that would be announced to compensate for the lack of aria-haspopup=dialog support. So per your question, I have both seen, and when appropriate have recommended, that visually hidden text like “opens dialog” be appended to the end of the accessible name of a button to provide context for people using screen readers.

Scott O'Hara says:

Hi Karin,

Much of what you’re asking I answered in my response to Andrea.

Though to expand on that, per some of your additional questions:

The ‘disorientation’ you describe is actually the default behavior when loading a new document, or programmatically focusing a container element (like a dialog), depending on the screen reader and browser pairing. These users likely wouldn’t be confused, as they would be well aware of when they are navigating a document vs when the screen reader is automatically reading the contents for them.

Moving on, I can’t give you a single definitive answer as to where focus placement should be for dialogs, as there are situations where it could be more appropriate to auto-focus an element within the dialog. But, clearly I generally recommend focusing the dialog itself. Again, I refer you to my previous response where I outline reasoning as to why.

Finally, I agree with you that dialogs should have a heading which can be used to provide an accessible name for the dialog. Though, I do think there can be situations where that heading may be visually hidden, so that it can still provide context to screen readers, but may not be always necessary visually. Again, it depends on the context and if the purpose of the dialog can be understood by other visual means. Regarding what should be focusable when there’s a visually hidden heading, I’d still defer to the dialog. Related, if you want to see a rundown of how screen readers announce a programmatically focused heading element, checkout modal test 1b details on my modal test page.

Thanks

Jon Avila says:

Use of aria-describedby should prevent dialog text from being announced verbosly in some situations mentioned. Additionally, I tend to believe that focus should not be set to the dialog itself as I have seen focus getting trapped outside the dialog in some cases. Focus should ideally go to first element such as form field or heading if a confirmation dialog. Also do to the issue on iOS with aria-modal causing so many issues perhaps a role of region instead of dialog is a viable solution?

Scott O'Hara says:

Hi Jon,

Thanks for reading. In response to your points:

In testing I also noticed that aria-describedby could cut down on verbosity, but only if a form control was autofocused in the dialog. This is definitely good behavior for dialogs that are lean on content, which I implied with “Where focus is initially placed may vary depending on the dialog’s content”. However this can become complex to manage if there are multiple text nodes that would describe the dialog, or inappropriate for dialogs with lots of content.

The most verbose test case I found was using NVDA and focusing the first heading element in the dialog. Focusing the dialog itself was not overly verbose in most other cases. And in those situations, it was the screen reader automatically reading the contents of the dialog. The announcements could be stopped, and was little different than if these screen readers were parsing a new document.

I’ve also seen focus getting trapped outside of dialogs. These were primarily due to incorrect implementation, or the iOS bug where trying to set focus to an element that was previously set to display: none doesn’t move VoiceOver focus. The VoiceOver focus bug isn’t unique to dialogs though, and can be worked around via the CSS I mentioned in the post, or by setting a brief timeout as other commenters have suggested.
The biggest issues with dialogs on iOS are due to the bugged implementation of the aria-modal attribute, and the issue with VoiceOver focus not being moved to elements that were initially set to display: none. Unfortunately, using a role of region over dialog doesn’t solve either of these issues.

Mike Elledge says:

Hi Scott–

Thanks for the comprehensive analysis and discussion.

I’ve always assumed that putting focus on the first focusable element meant the first natively focusable element, i.e., the first button or input field in the dialog. This is in contrast to something not typically actionable like a heading being given focus, for example by using tabindex=”0″. The rationale, again my assumption, is that context is provided to the user when the dialog heading is announced, and that putting focus into the first form field facilitates the process of filling out the form. I haven’t done user testing, but this seems to be the more conventional approach and would better meet user expectations.

Your thoughts?

Hi, Scott.

Thanks for replying to my question. You mentioned that your previous reply answered my questions — actually, after I read that reply is when I sat down to write to you. 🙂 It got me thinking.

I think my confusion is just how the user and screen reader work together to perceive the content of the dialog. In short, if the whole dialog is focused rather than an element, 1) the text in the dialog isn’t being read; 2) the user can’t use the browse mode to use headings and key commands to read the dialog (and NVDA won’t read the dialog by itself); 3) pressing tab to a focusable element puts the screen reader into browse mode (I am guessing that this is happening); and 4) the user won’t know that there’s a close button etc. until they press Tab.

In the dialogs I’ve been testing (using NVDA), when the focus is set to the entire dialog, the only thing that happens when the dialog opens is that the aria-label added to the

containing the dialog is read. (1) The text in the dialog is NOT read, and (2) you can’t use the browse mode (e.g., pressing H doesn’t find and read the heading, pressing the down arrow doesn’t read the text in the dialog).

Pressing Tab takes focus to the close button, and then (3) NVDA reads all the content in the dialog (close button, explanatory text, 2nd close button). This is not achieved with ARIA — the text is just read because NVDA is reading the content in the dialog. and then you can use the browse mode (H for headings, up/down arrows to read the contents of the dialog).

At this point, since the first focusable element in my dialogs is always an X button in the dialog, at that point the user knows how to close the dialog. To me, this means that to read the text in the dialog and then close the dialog, the user would need to first know that there’s a close button and then do another action to Tab to it. In other words, the close buttons aren’t revealed to the user until Tab is pressed.

Isn’t this behind the idea that it’s best to make the button focusable and “pull in” the title, explanatory text, etc, via ARIA? Then you’re in the focus “layer” and can use browse key commands to move around (in case you want to read the dialog again), you know about the close button, and all the text gets read.

Shortest version of my question: Am I correct in my conclusion that, when a dialog receives focus, they are NOT in the screen reader browse mode, but when an element receives focus, they are?

And if it’s still better to focus on the whole dialog, is pulling in the title of the dialog and text via ARIA (describedby, added to the dialog-containing div) the way to go?

Thanks very much for suffering through my question — this is important to me to really understand so I can give good advice to my cohorts.

Karin

Scott O'Hara says:

Hi Karin,

A big issue with custom dialogs is that they are not consistently implemented. Without actually being able to interact with the dialog you’re describing, I can only speculate as to what the issues are. What I can respond with, when using the version of the script I built and tested with, I (and others who tested, both other a11y testers and people used who use screen readers on a daily basis) encountered the following:

When setting focus to the dialog, while using NVDA (2017.4 – 2018.2.1), NVDA remains in browse mode. I test with default settings, so NVDA will automatically go in and out of app mode as it encounters elements that require it to do so. When the dialog is focused, NVDA announces the accessible name of the dialog, but does not announce the dialog role (as you also found). I recorded this in my tests, and have an open ticket with NVDA about that. Depending on the browser you’ve paired NVDA with (e.g. Chrome, or Firefox), NVDA will begin sequentially reading all the content of the dialog. At any point in time, hitting the control key will stop NVDA from reading and the virtual cursor will pause in that position. Hot keys can be used immediately to move to elements within the dialog.

I invite you to check out the repo and test files I linked to in the article, and the comment we both referenced.

Thanks

Scott O'Hara says:

Hi Mike,

Apologies for the delayed reply.

I hope to write more about this later, so please forgive me for being brief with this response. Your assumptions are not incorrect when a dialog contains a form. However there are many different types of content that a dialog could contain (for better or worse). As one example, a confirmation dialog could contain a company’s terms of service. In dialogs like this, a confirmation button is typically located at the very end of the terms so that users “read” or at least scroll through all the content. Setting keyboard focus to this button would auto-scroll the dialog, which is undesired. Thus in situations like this, an element other than the confirmation button would need to be focused.

The type of content within a dialog would dictate which sort of element initial focus placement should be set to, e.g. a button, form control or heading (all of this outlined in the ARIA practices document). It’s this variability that can alter user expectations for dialogs, and can lead to frustration when a dialog’s start point can feel unpredictable, especially considering multiple dialogs of different types might be available in a user interface.

Again, more on this later.

Scott O'Hara says:

Update:

Based on testing with VoiceOver and macOS Safari Tech Preview: Release 63 (Safari 12.1, WebKit 13607.1.2.1), the issue with aria-modal=”true” should be solved.

Rich says:

In my testing with your dialog example, NVDA+Firefox does report the dialog role.

Also, I do not like the typical screen reader default behavior of reading the entire document when it becomes available, either when a new page is loaded in the browser window itself, or when a dialog or other popup gains focus. I always turn this behavior off in my screen reader (both Jaws and NVDA allow one to do this). Once this is done, your dialog example reports the dialog role and then reads the heading. Thsi makes it perfectly clear what is happening and allows the user to consume the content any way she likes.

Also, as I think you pointed out above, NVDA does now automatically enter browse mode when the dialog (or anything inside of it) gains focus, unless that item is an element (like a text box) which requires app mode, and then it does automatically switch.

Thanx for this very thurough article!

Scott O'Hara says:

Heya Rich,

Thank you for your feedback and insight here. It’s very much appreciated!

Regarding hearing the dialog role with NVDA, that’s interesting to know. With the Say All option checked, the role is not announced. But as you said, if the option is unchecked, the dialog role is announced as expected.

Thanks again!

What should I use to label a modal that doesn’t have a visible title (just a modal body)? On the modal, I’m using the following attributes – role=”dialog” aria-modal=”true” aria-label=”This is the modal title”. When I use aria-label (and not aria-labelledby), only the label is read and the modal body is ignored. If I use aria-labelledby, it works just fine.

Michael, in what browser / screen reader combination are you experiencing this?

James says:

Re: solving the Safari/VO focus issue, if you double up the requestAnimationFrame call (i.e., call requestAnimationFrame inside a call to requestAnimationFrame), I believe it should guarantee success—although I haven’t tested.

For an explanation on why that works for situations like this, check out this post from Paul Irish: https://medium.com/@paul_irish/requestanimationframe-scheduling-for-nerds-9c57f7438ef4