├── .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 |
13 |

The Incredible Accessible Modal Window, Version 4

14 |

Get the code on GitHub

15 |

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 | 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 --------------------------------------------------------------------------------