Skip to content

Commit 7c36750

Browse files
committed
[fixes] Improve accessibility, fixes #359
- adds aria-modal="true" to modal portal - doesn't make body a default appElement - warns if appElement is not set in any way
1 parent 700eb4e commit 7c36750

File tree

4 files changed

+23
-42
lines changed

4 files changed

+23
-42
lines changed

README.md

-3
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ to prevent assistive technologies such as screenreaders
5757
from reading content outside of the content of
5858
your modal.
5959

60-
It's optional and if not specified it will try to use
61-
`document.body` as your app element.
62-
6360
If you are doing server-side rendering, you should use
6461
this property.
6562

specs/Modal.spec.js

-9
Original file line numberDiff line numberDiff line change
@@ -339,15 +339,6 @@ export default () => {
339339
unmountModal();
340340
});
341341

342-
it("uses document.body for aria-hidden if no appElement", () => {
343-
ariaAppHider.documentNotReadyOrSSRTesting();
344-
const node = document.createElement("div");
345-
ReactDOM.render(<Modal isOpen />, node);
346-
document.body.getAttribute("aria-hidden").should.be.eql("true");
347-
ReactDOM.unmountComponentAtNode(node);
348-
should(document.body.getAttribute("aria-hidden")).not.be.ok();
349-
});
350-
351342
it("raises an exception if the appElement selector does not match", () => {
352343
should(() => ariaAppHider.setElement(".test")).throw();
353344
});

src/components/ModalPortal.js

+7-10
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ export default class ModalPortal extends Component {
9999
}
100100

101101
componentWillUnmount() {
102-
// Remove body class
103-
bodyClassList.remove(this.props.bodyOpenClassName);
104-
this.beforeClose();
102+
this.afterClose();
105103
clearTimeout(this.closeTimer);
106104
}
107105

@@ -127,17 +125,16 @@ export default class ModalPortal extends Component {
127125
}
128126
}
129127

130-
beforeClose() {
128+
afterClose = () => {
131129
const { appElement, ariaHideApp } = this.props;
130+
131+
// Remove body class
132+
bodyClassList.remove(this.props.bodyOpenClassName);
133+
132134
// Reset aria-hidden attribute if all modals have been removed
133135
if (ariaHideApp && refCount.totalCount() < 1) {
134136
ariaAppHider.show(appElement);
135137
}
136-
}
137-
138-
afterClose = () => {
139-
// Remove body class
140-
bodyClassList.remove(this.props.bodyOpenClassName);
141138

142139
if (this.props.shouldFocusAfterRender) {
143140
if (this.props.shouldReturnFocusAfterClose) {
@@ -171,7 +168,6 @@ export default class ModalPortal extends Component {
171168
};
172169

173170
close = () => {
174-
this.beforeClose();
175171
if (this.props.closeTimeoutMS > 0) {
176172
this.closeWithTimeout();
177173
} else {
@@ -309,6 +305,7 @@ export default class ModalPortal extends Component {
309305
onClick={this.handleOverlayOnClick}
310306
onMouseDown={this.handleOverlayOnMouseDown}
311307
onMouseUp={this.handleOverlayOnMouseUp}
308+
aria-modal="true"
312309
>
313310
<div
314311
ref={this.setContentRef}

src/helpers/ariaAppHider.js

+16-20
Original file line numberDiff line numberDiff line change
@@ -19,42 +19,38 @@ export function setElement(element) {
1919
return globalElement;
2020
}
2121

22-
export function tryForceFallback() {
23-
if (document && document.body) {
24-
// force fallback to document.body
25-
setElement(document.body);
26-
return true;
27-
}
28-
return false;
29-
}
30-
3122
export function validateElement(appElement) {
32-
if (!appElement && !globalElement && !tryForceFallback()) {
33-
throw new Error(
23+
if (!appElement && !globalElement) {
24+
// eslint-disable-next-line no-console
25+
console.warn(
3426
[
35-
"react-modal: Cannot fallback to `document.body`, because it is not",
36-
"ready or available. If you are doing server-side rendering, use this",
37-
"function to defined an element. `Modal.setAppElement(el)` to make",
38-
"this accessible"
27+
"react-modal: App element is not defined.",
28+
"Please use `Modal.setAppElement(el)` or set `appElement` property."
3929
].join(" ")
4030
);
31+
32+
return false;
4133
}
34+
35+
return true;
4236
}
4337

4438
export function hide(appElement) {
45-
validateElement(appElement);
46-
(appElement || globalElement).setAttribute("aria-hidden", "true");
39+
if (validateElement(appElement)) {
40+
(appElement || globalElement).setAttribute("aria-hidden", "true");
41+
}
4742
}
4843

4944
export function show(appElement) {
50-
validateElement(appElement);
51-
(appElement || globalElement).removeAttribute("aria-hidden");
45+
if (validateElement(appElement)) {
46+
(appElement || globalElement).removeAttribute("aria-hidden");
47+
}
5248
}
5349

5450
export function documentNotReadyOrSSRTesting() {
5551
globalElement = null;
5652
}
5753

5854
export function resetForTesting() {
59-
globalElement = document.body;
55+
globalElement = null;
6056
}

0 commit comments

Comments
 (0)