├── .gitignore
├── README.md
├── index.html
├── modal-window.css
├── modal-window.js
└── x.png
/.gitignore:
--------------------------------------------------------------------------------
1 | /nbproject/
2 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Incredible Accessible Modal Window
2 |
3 | This page demonstrates how to make a modal window as accessible as possible to assistive technology users. Modal windows are especially problematic for screen reader users. Often times the user is able to "escape" the window and interact with other parts of the page when they should not be able to. This is partially due to the way screen reader software interacts with the Web browser.
4 |
5 | ## What's New In Version 4?
6 |
7 | * Due to high demand, the role="document" is added back to the contents of the modal. This makes it so NVDA automatically switches into document reading mode inside of the modal. NVDA had previously let you toggle the reading mode, but since many modals contain items that require document browsing mode, I've added this back in as the default.
8 | * There is now a check that when the modal window is open, detects any time the #mainPage or any of its contents receives focus and will redirect the focus to the modal window. This was necessary because of the modal window was open and you went to the address bar, if you started tabbing again you would interact with the main page.
9 | * Both the documentation and the [live demo](http://gdkraus.github.io/accessible-modal-dialog/) now live on GitHub.
10 |
11 | ##Features
12 |
13 | This example implements the following features:
14 |
15 | 1. The page is divided into three sections:
16 | 1.
18 | 3.
19 | 2. When the modal dialog is displayed, an overlay is placed over top of the mainPage so it is
20 | 1. visually grayed out in order to indicate you cannot interact with what is behind the window
21 | 2. not clickable with the mouse
22 | 3. When the modal dialog is displayed, the mainPage is marked with aria-hidden="true" to prevent screen readers from interacting with it once the modal dialog is open. Additionally, the mainPage gets an even listener for any time it or any of its children receive focus. When they do receive focus, the user's focus is redirected to the modal window.
23 | 4. Keyboard access is limited to only interacting with the modal dialog once it is visible
24 | 1. The tab key loops through all of the keyboard focusable items within the modal window
25 | 2. This is determined programmatically through the DOM each time the tab key is pressed so you do not have to create an explicit list of focusable items within the modal window to keep track of
26 | 3. The escape key is mapped to the function to close the modal window
27 | 4. The enter key is mapped to the submit function of the modal window
28 | 5. The title of the modal dialog is identified through the aria-labelledby attribute.
29 | 6. The beginning of the modal dialog is marked with an h1
30 | 7. There are offscreen instructions that describe the modal dialog and how to interact with it
31 | 1. The instructions are attached through the aria-describedby attribute.
32 | 2. Only NVDA and ChromeVox automatically announce the aria-described by contents
33 | 8. The contents of the modal are wrapped in role="document". This is to aid NVDA users so they can more easily browse the contents of the modal. NVDA previously added support for fully browsing the contents of the modal, but it requires the user to switch browsing modes in NVDA. Using role="document" automatically puts the user in the mode where they can fully browse the contents.
34 | 1. role="document" is optional and the modal window is still fully functional to all users even when this attribute is left off.
35 | 9. Configurations Tested
36 | 1. JAWS 16.0.1925 in IE 11.0.9600.18036 in Windows 8.1: Passed - although aria-describedby is not read automatically
37 | 2. NVDA 2015.3 in Firefox 40.0.3 in Windows 8.1: Passed
38 | 3. Window Eyes 9.2.1.0 in 11.0.9600.18036 in Windows 8.1: Passed - although the title of the modal window and the aria-describedby is not read automatically
39 | 4. VoiceOver in Safari 8.0.8 (9537.71) in OS X 10.10.5: Passed - although aria-describedby is not read automatically
40 | 5. ChromeVox 45.0.2428.0 in Chrome 45.0.2454.101 in OS X 10.10.5: Passed
41 | 6. Orca 3.10.3 in Firefox 39.0 in Ubuntu 14.04: Partial Functionality - does not support aria-hidden and does not announce the aria-describedby
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | The Incredible Accessible Modal Window, Version 3
6 |
7 |
8 |
9 |
10 |
11 |
12 |
This page demonstrates how to make a modal window as accessible as possible to assistive technology users. Modal windows are especially problematic for screen reader users. Often times the user is able to "escape" the window and interact with other parts of the page when they should not be able to. This is partially due to the way screen reader software interacts with the Web browser.
16 |
The Accessible Modal Window in Action
17 |
To see this in action, you just need to . If the modal window works as planned, once the modal window is visible you should not be able to interact with other links on the main page like going to the main GitHub page. If you can interact with the page behind the modal window, guidance is given for how to get back to the modal window.
18 |
19 |
20 |
21 |
Beginning of dialog window. It begins with a heading 1 called "Registration Form". Escape will cancel and close the window. This form does not collect any actual information.
22 |
Registration Form
23 |
These are the onscreen instructions that are not attached explicitly to a focusable element. Can screen reader users read this text with the virtual cursor?
24 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/modal-window.css:
--------------------------------------------------------------------------------
1 | #modalOverlay {
2 | width:100%;
3 | height:100%;
4 | z-index:2; /* places the modal overlay between the main page and the modal dialog*/
5 | background-color:#000;
6 | opacity:0.5;
7 | position:fixed;
8 | top:0;
9 | left:0;
10 | display:none;
11 | margin:0;
12 | padding:0;
13 | }
14 |
15 | #modal {
16 | width:50%;
17 | margin-left:auto;
18 | margin-right:auto;
19 | padding: 5px;
20 | border: thin #000 solid;
21 | background-color:#fff;
22 | z-index:3; /* places the modal dialog on top of everything else */
23 | position:fixed;
24 | top:25%;
25 | left:25%;
26 | display:none;
27 | }
28 | #modal h1 {
29 | text-align:center;
30 | }
31 |
32 | .modalCloseButton {
33 | float:right;
34 | position:absolute;
35 | top:10px;
36 | left:92%;
37 | height:25px;
38 | }
39 | .modalCloseButton img {
40 | border:0;
41 | }
42 |
43 | .screen-reader-offscreen {
44 | position:absolute;
45 | left:-999px;
46 | width:1px;
47 | height:1px;
48 | top:auto;
49 | }
50 |
--------------------------------------------------------------------------------
/modal-window.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | ============================================
4 | License for Application
5 | ============================================
6 |
7 | This license is governed by United States copyright law, and with respect to matters
8 | of tort, contract, and other causes of action it is governed by North Carolina law,
9 | without regard to North Carolina choice of law provisions. The forum for any dispute
10 | resolution shall be in Wake County, North Carolina.
11 |
12 | Redistribution and use in source and binary forms, with or without modification, are
13 | permitted provided that the following conditions are met:
14 |
15 | 1. Redistributions of source code must retain the above copyright notice, this list
16 | of conditions and the following disclaimer.
17 |
18 | 2. Redistributions in binary form must reproduce the above copyright notice, this
19 | list of conditions and the following disclaimer in the documentation and/or other
20 | materials provided with the distribution.
21 |
22 | 3. The name of the author may not be used to endorse or promote products derived from
23 | this software without specific prior written permission.
24 |
25 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
26 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
28 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 |
35 | */
36 |
37 | // jQuery formatted selector to search for focusable items
38 | var focusableElementsString = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";
39 |
40 | // store the item that has focus before opening the modal window
41 | var focusedElementBeforeModal;
42 |
43 | $(document).ready(function() {
44 | jQuery('#startModal').click(function(e) {
45 | showModal($('#modal'));
46 | });
47 | jQuery('#cancel').click(function(e) {
48 | hideModal();
49 | });
50 | jQuery('#cancelButton').click(function(e) {
51 | hideModal();
52 | });
53 | jQuery('#enter').click(function(e) {
54 | enterButtonModal();
55 | });
56 | jQuery('#modalCloseButton').click(function(e) {
57 | hideModal();
58 | });
59 | jQuery('#modal').keydown(function(event) {
60 | trapTabKey($(this), event);
61 | })
62 | jQuery('#modal').keydown(function(event) {
63 | trapEscapeKey($(this), event);
64 | })
65 |
66 | });
67 |
68 | function trapEscapeKey(obj, evt) {
69 |
70 | // if escape pressed
71 | if (evt.which == 27) {
72 |
73 | // get list of all children elements in given object
74 | var o = obj.find('*');
75 |
76 | // get list of focusable items
77 | var cancelElement;
78 | cancelElement = o.filter("#cancel")
79 |
80 | // close the modal window
81 | cancelElement.click();
82 | evt.preventDefault();
83 | }
84 |
85 | }
86 |
87 | function trapTabKey(obj, evt) {
88 |
89 | // if tab or shift-tab pressed
90 | if (evt.which == 9) {
91 |
92 | // get list of all children elements in given object
93 | var o = obj.find('*');
94 |
95 | // get list of focusable items
96 | var focusableItems;
97 | focusableItems = o.filter(focusableElementsString).filter(':visible')
98 |
99 | // get currently focused item
100 | var focusedItem;
101 | focusedItem = jQuery(':focus');
102 |
103 | // get the number of focusable items
104 | var numberOfFocusableItems;
105 | numberOfFocusableItems = focusableItems.length
106 |
107 | // get the index of the currently focused item
108 | var focusedItemIndex;
109 | focusedItemIndex = focusableItems.index(focusedItem);
110 |
111 | if (evt.shiftKey) {
112 | //back tab
113 | // if focused on first item and user preses back-tab, go to the last focusable item
114 | if (focusedItemIndex == 0) {
115 | focusableItems.get(numberOfFocusableItems - 1).focus();
116 | evt.preventDefault();
117 | }
118 |
119 | } else {
120 | //forward tab
121 | // if focused on the last item and user preses tab, go to the first focusable item
122 | if (focusedItemIndex == numberOfFocusableItems - 1) {
123 | focusableItems.get(0).focus();
124 | evt.preventDefault();
125 | }
126 | }
127 | }
128 |
129 | }
130 |
131 | function setInitialFocusModal(obj) {
132 | // get list of all children elements in given object
133 | var o = obj.find('*');
134 |
135 | // set focus to first focusable item
136 | var focusableItems;
137 | focusableItems = o.filter(focusableElementsString).filter(':visible').first().focus();
138 |
139 | }
140 |
141 | function enterButtonModal() {
142 | // BEGIN logic for executing the Enter button action for the modal window
143 | alert('form submitted');
144 | // END logic for executing the Enter button action for the modal window
145 | hideModal();
146 | }
147 |
148 | function setFocusToFirstItemInModal(obj){
149 | // get list of all children elements in given object
150 | var o = obj.find('*');
151 |
152 | // set the focus to the first keyboard focusable item
153 | o.filter(focusableElementsString).filter(':visible').first().focus();
154 | }
155 |
156 | function showModal(obj) {
157 | jQuery('#mainPage').attr('aria-hidden', 'true'); // mark the main page as hidden
158 | jQuery('#modalOverlay').css('display', 'block'); // insert an overlay to prevent clicking and make a visual change to indicate the main apge is not available
159 | jQuery('#modal').css('display', 'block'); // make the modal window visible
160 | jQuery('#modal').attr('aria-hidden', 'false'); // mark the modal window as visible
161 |
162 | // attach a listener to redirect the tab to the modal window if the user somehow gets out of the modal window
163 | jQuery('body').on('focusin','#mainPage',function() {
164 | setFocusToFirstItemInModal(jQuery('#modal'));
165 | })
166 |
167 | // save current focus
168 | focusedElementBeforeModal = jQuery(':focus');
169 |
170 | setFocusToFirstItemInModal(obj);
171 | }
172 |
173 | function hideModal() {
174 | jQuery('#modalOverlay').css('display', 'none'); // remove the overlay in order to make the main screen available again
175 | jQuery('#modal').css('display', 'none'); // hide the modal window
176 | jQuery('#modal').attr('aria-hidden', 'true'); // mark the modal window as hidden
177 | jQuery('#mainPage').attr('aria-hidden', 'false'); // mark the main page as visible
178 |
179 | // remove the listener which redirects tab keys in the main content area to the modal
180 | jQuery('body').off('focusin','#mainPage');
181 |
182 | // set focus back to element that had it before the modal was opened
183 | focusedElementBeforeModal.focus();
184 | }
--------------------------------------------------------------------------------
/x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdkraus/accessible-modal-dialog/d2a9c13de65028cda917279246346a277509fda0/x.png
--------------------------------------------------------------------------------