-
Notifications
You must be signed in to change notification settings - Fork 809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
aria-hidden=true on body breaks Accessibility <body class="ReactModal__Body--open" aria-hidden="true"> #359
Comments
There's a new aria-modal=true attribute that should be placed on the modal dialog which could remove the need for aria-hidden. |
@pauljadam can you elaborate and give some advises about accessibility? |
http://w3c.github.io/aria/aria/aria.html#aria-hidden aria-hidden (state) Indicates whether the element is exposed to an accessibility API. If the body is hidden, then the whole body tag is no longer exposed to an Accessibility API. You only want to hide the main content container(plus header/footer) of the page not the entire tag. |
http://w3c.github.io/aria-practices/#dialog_modal This demo uses aria-modal=true rather than aria-hidden. http://w3c.github.io/aria-practices/examples/dialog-modal/dialog.html |
I can confirm this behavior. It also looks like focus management isn't happening on the modal, and it's missing the dialog role (though those could be separate issues). Here's how to fix it:
Following the ARIA practices guide is a great suggestion! |
Thank you so much @pauljadam and @marcysutton. |
Just wanna weigh in here for a moment. There is a lot of confusion around this behavior in the modal. I've been working to improve documentation and make things more clear in our 2.0 release which is overdue unfortunately. I can assure you that when used properly the modal is accessible. We've used it extensively in Canvas. @pauljadam The example you linked to is the minimal example. It is the minimal needed to make the modal show up and function. react-modal uses the concept of the <body>
<div id="application">
....
</div>
</body> In this case, we'd want to set the appElement to <body>
<div id="application" aria-hidden="true">
....
</div>
<div class="ReactModalPortal">
...
</div>
</body> That would keep screen readers inside the modal. In version 2.0 of the modal Thanks also for pointing out @marcysutton Thanks for adding your input here. It's super valuable! When Ryan initially created the modal the state of I'm not sure why we default to setting the focus to the body. I'm sure @ryanflorence might recall why it was done. That being said, we do provide an Sorry all, this was a bit long winded. Issues like this are very tough for me. They open my eyes more to the need to have better documentation around different assumptions the modal makes. @diasbruno - I don't think there's much we can do about this on our 1.x branch. I think things will be much better and clearer in 2.0. |
@claydiffrient what about when you have a header and footer that you need to hide also? Can you do setAppElement on multiple html elements? e.g. http://codepen.io/anon/pen/jBxwZr aria-modal=true looks like it would eliminate the need to manage aria-hidden So far all the client sites I've tested out in the wild are not using setAppElement and it just hides the body so kills the whole page when the modal appears. |
@pauljadam Currently, it doesn't in all the use cases I've needed it for, there as always been a root-level non-body application container. What we can do about that though is make it so aria-modal does look really promising, but that also requires that the modal have a role of dialog which has concerns with screen readers that I mentioned in my lat comment. I'm sad to hear that about the client sites. I want to fix that so badly, but it now ends up being a breaking change which necessitates a major version bump. We've done a poor job of documenting the need for that, something I hope to rectify. |
@claydiffrient I would still recommend role=dialog for normal dialogs and role=alertdialog for error type dialogs. This is what the aria spec and practices recommends. I read the post from 2014 about the worries of role=dialog, that issue has no affect on macOS VoiceOver screen reader or mobile screen readers VoiceOver iOS or Android TalkBack. Only JAWS and NVDA include the automatic switching to application mode vs browse mode and they can switch back to browse mode if they're in application mode, I cover that issue in this blog post under the NVDA Forms Mode Behavior & Insert + Spacebar Key heading http://www.deque.com/blog/aria-modal-alert-dialogs-a11y-support-series-part-2/ In a normal role=”dialog” or “alertdialog” NVDA will go into Forms Mode when focus is sent into the dialog. Forms Mode means NVDA users an only tab around the dialog. They can’t press the up/down arrow keys to read through the dialog with linear navigation. To exit forms mode NVDA users can press Insert + Spacebar keys and then enter Browse Mode where they can use the arrow keys and quick navigation keys to read through the dialog rather than just the TAB key. Most accessibility folks are confused by this behavior and wonder if screen reader users understand how the read the dialog or if the dialog code is broken because it’s not working properly with NVDA. This is similar to the problems with using role=”application”. Forms Mode/Browse mode issues don’t affect other screen readers like VoiceOver and TalkBack. I’ve heard that JAWS isn’t affected by the role=”alertdialog” Forms Mode issue like NVDA. |
So arguably it's a better experience for SR users to not deal with switching modes. I worked at one point with a blind engineer who suggested doing it the way we currently have it implemented. I guess it's all a matter of which is better to default to. Currently we default to no role and allow consumers to add a role. We could in theory default to having the role=dialog and then allow overrides from users to take if off if they felt things would be better the other way. I personally like the idea of not having to worry about switching modes for any users. Sure it's not exactly in line with the ARIA Authoring Practices, but... as you said in your article, "Sometimes it makes sense to bend the rules of the ARIA Authoring Practices." I think this is perhaps one of those times. |
Please note that this example with aria-modal: does not work for users of IE11. While that might be tempting to justify, IE11 is a browser used quite a bit by blind users, especially given that Edge is nowhere near ready for production for these users. As a result, I would strongly recommend making sure everything that is not modal receive an aria-hidden="true". The above example does work with Jaws 18 and the latest Firefox (I did not test with Chrome nor with NVDA and other browsers, though I could if desired). |
I would just say that I think it's very important to use role=dialog and role=alertdialog to convey the accessibility semantics of the element. It's also the only way to give the dialog an accessible name and description via aria-labelledby and aria-describedby. This is something I regularly treat as an accessibility failure for client websites if it's not present. The 2 roles speak different semantics to a screen reader user, e.g. alertdialog says "Alert" on some screen readers when you enter the dialog. |
@sinabahram I was able to get the example working in IE 11 Win 10 using NVDA latest, it appears to trap the modal dialog with NVDA whereas with Narrator you can still navigate to the grayed out content so maybe NVDA likes aria-modal="true" and Narrator still not supporting it. Narrator also has no issues with forms mode or browse mode. |
I can see that, but given the two roles which do we use? The modal is meant to be general purpose so it could potentially be used with either option. So do we continue with what we have with role being optional and up to the client to implement? I think perhaps that might be the best course of action. Consumers will be able to specify if they want |
If they're setting the role they should also set the accessible name either via aria-labelledby pointing to the h1 in the dialog or an aria-label if there is no visible title for the dialog. Accessible description via aria-describedby should also be set if possible. If you're adding the accessible name, role, and description, should also do the ARIA 1.1 aria-modal=true attribute. The biggest problem is still that aria-hidden=true is ending up on the body tag in all of the examples of this modal dialog I've seen in the wild during Accessibility testing. I figured it had to be in a widely used framework after seeing on different sites so I googled "ReactModal__Body--open" and ended up here :) I thought maybe I could fix it at the source rather than telling all my clients individually that aria-hidden has broken their entire application :) |
I can totally see that. I'll see what I can do about the role and other aria attributes. In version 2.0 of the modal body will not be hidden, that I can for sure promise. |
This makes getAppElement a required prop as well as makes it a function that will be called expecting a DOMElement. closes #287 This also takes some inspiration from #359 for handling arrays of objects. Upgrade Path: - If you had specified an appElement via `Modal.setAppElement`, then you need to convert that to a getAppElement prop on the modal, this should be a function that returns either a single element or an array of elements. - If you had nothing specified you will need to add the getAppElement element to prevent beakages.
This makes getAppElement a required prop as well as makes it a function that will be called expecting a DOMElement. closes #287 This also takes some inspiration from #359 for handling arrays of objects. Upgrade Path: - If you had specified an appElement via `Modal.setAppElement`, then you need to convert that to a getAppElement prop on the modal, this should be a function that returns either a single element or an array of elements. - If you had nothing specified you will need to add the getAppElement element to prevent beakages.
Testing was done with Jaws and IE11, a very popular combination. For nvDA, of course one uses Firefox and Chrome instead of IE given the myriad of performance issues and far better support on Firefox and Chrome.
Jaws18+IE11 does not hide the virtual content outside of the modal.
|
This makes getAppElement a required prop as well as makes it a function that will be called expecting a DOMElement. closes #287 This also takes some inspiration from #359 for handling arrays of objects. Upgrade Path: - If you had specified an appElement via `Modal.setAppElement`, then you need to convert that to a getAppElement prop on the modal, this should be a function that returns either a single element or an array of elements. - If you had nothing specified you will need to add the getAppElement element to prevent beakages.
This makes getAppElement a required prop as well as makes it a function that will be called expecting a DOMElement. closes #287 This also takes some inspiration from #359 for handling arrays of objects. Upgrade Path: - If you had specified an appElement via `Modal.setAppElement`, then you need to convert that to a getAppElement prop on the modal, this should be a function that returns either a single element or an array of elements. - If you had nothing specified you will need to add the getAppElement element to prevent beakages.
Worth nothing About the Basically, when using Think at a modal with content made exclusively by some long text or images without any interactive elements. It could be a warning, or some long inline help, whatever. Using Also about
|
aria-modal=true works in VoiceOver on iOS and macOS. It worked in NVDA also. Have you tested the ARIA authoring practices demo? aria-hidden=true on the body is still the major problem here. |
Hi @Ghorthalon, maybe it was missing to do something about it. So, what next to improve react-modal accessibility:
|
This issue is still open, was there ever a resolution? It's a pretty big show-stopper, so it would be good to get the docs updated or whatever else needs to happen. |
This week, I'm going to start a new branch to address this accessibility issues. Testing and review would be really appreciated. |
@diasbruno aria-hidden is not removed at all, because you check for reference count in beforeClose: https://github.com/reactjs/react-modal/blob/master/src/components/ModalPortal.js#L133 but you decrement this counter is afterClose: https://github.com/reactjs/react-modal/blob/master/src/components/ModalPortal.js#L140 |
@sheerun It make sense. So we need to move the logic on |
- adds aria-modal="true" to modal portal - doesn't make body a default appElement - warns if appElement is not set in any way
@diasbruno I've sent one. Could you finish it? I don't have any more time today It adds aria-modal to modal portal and removes adding aria-hidden to body by default. Also fixes issue I mentioned. |
Sure. Thank you! |
- adds aria-modal="true" to modal portal - doesn't make body a default appElement - warns if appElement is not set in any way
- adds aria-modal="true" to modal portal - doesn't make body a default appElement - warns if appElement is not set in any way
- adds aria-modal="true" to modal portal - doesn't make body a default appElement - warns if appElement is not set in any way
@pauljadam @marcysutton @afercia If you'd like to test it before @diasbruno releases it, I've published it under following package name on npm: |
- adds aria-modal="true" to modal portal - doesn't make body a default appElement - warns if appElement is not set in any way
- adds aria-modal="true" to modal portal - doesn't make body a default appElement - warns if appElement is not set in any way
- adds aria-modal="true" to modal portal - doesn't make body a default appElement - warns if appElement is not set in any way fixes #359
Released |
Just to be clear, is it unacceptable in all situations to have I am asking because I'm building a component library for my company, and since many teams will be using it I cannot guess (or constrict) what their React root element is going to be. Even though this component library will mostly be used internally for the foreseeable future, I don't want to make things inaccessible if I can avoid it. One solution I thought of was just using the Another solution was to have ReactModal as a named export of the component library so that implementors could have Basically my question is what is the status quo for setting app element when it is unknown what the app element is going to be? UPDATE (7/16/2018): The solution I landed on was actually just exposing the same static property in my custom modal class MyCustomModal extends Component {
+ static setAppElement(element) {
+ if (element) {
+ ReactModal.setAppElement(element);
+ }
+ }
} This way implementors of the modal will only have to set the app element once per application, rather than on every element instance (of course the If someone sees a major issue with the solution I arrived at for my modal component wrapper, please let me know. |
This should be closed, no? |
@kylemh Google Lighthouse reports setting
|
Sure, but that would only happen if you incorrectly do: 38dc8f9 removed the ability for Notice this warning that exists when neither |
@diasbruno you closed and then reopened this, but announced a release as if the fix was out. I believe you meant for this to be closed because setting the I think one way to definitely mark this as closed would be to expand documentation or the dev warning here to ensure that nobody is hiding the body. |
@kylemh apologies, I must have been looking at an older version of
but looking at the the newer code, I see it is fixed:
I checked a small personal example and did see that Perhaps a new issue should be opened to update the docs. For example, the "minimal example" doc links to this codepen which seems to indicate that We also have docs/examples/set_app_element.md which says:
|
Thanks for confirming, @techieshark |
Summary:
Screen reader users cannot interact with any modal content because aria-hidden=true is incorrectly placed on the body tag.
Steps to reproduce:
Expected behavior:
Screen reader user is trapped inside the modal dialog.
Actual behavior:
Screen reader user can't interact with anything on the page at all because of aria-hidden=true on the body.
The text was updated successfully, but these errors were encountered: