Accessible Dialog Box

This page is under re-construction.... more information and updates will be added ...


HTML dialog boxes are created by displaying and styling page content as a dialog box (usually with lightbox effect) in the middle of the page.

Such dialog boxes are often not accessible - they might not capture the keyboard focus or it might not be clear to a screen reader user that a dialog box was opened or how to access it.

The code below is an example of an HTML dialog box with a good level of accessibility.

Try here:

When the trigger link is clicked, the following happens:

  1. The grey overlay and dialog box are made visible by switching the style to display:block;
  2. Keyboard focus is moved to the heading for the dialog box." at the top of the dialog box. This informs screen reader users that a dialog was opened (because the heading is inside a div with role="dialog'). This also places the reading point at the start of the dialog so keyboard and screen reader users can move down to read/interact with the dialog
  3. Child elements of the <body> element (except the dialog) are given the "aria-hidden" attribute

When the dialog box is closed:

  1. Child elements of the <body> element have aria-hidden values restored to previous values 
  2. Keyboard focus is moved back to the trigger link
  3. The grey overlay and dialog box is hidden by switching the style to display:none;

Modality

The dialog is modal for mouse and keyboard users, as the grey overlay prevents interaction with other page content for mouse users.

Keyboard event handlers are used to keep keyboard focus inside the dialog box. For screen reader users the modality is provided by applying the aria-hidden="true" attribute for all page content except the dialog box.

Code used

HTML

    <button onclick="openDialog('dia',this);">Test dialog</button>

    
<!-- other page content -->
    
<p>Lorem ipsum</p>
    
    
<!-- dialog box at bottom of page -->
    
<div id="dia" role="dialog" aria-labelledby="dia-heading" style="display:none;">
        
<div role="document">
            
<h1 id="dia-heading" tabindex="-1">
                Your heading text here
            
</h1>
            
<!-- dialog content start -->
            
            
<!-- first focusable element has class of "focus" -->
            
<button class="focus">Select</button>

            
<!-- dialog content end -->
            
<button class="close" aria-label="Close dialog">X</button>
        
</div>
    
</div>
</body>

CSS

div[role="dialog"]{
    position
: fixed;
    top
: 0;
    left
: 0;
    width
: 100%;
    height
:100%;
    z
-index: 999999;
    background
-color: rgba(0,0,0,0.6);
}

div
[role="document"]{
    position
: fixed;
    left
:50%;
    top
:50%;
    transform
: translate(-50%, -50%);
    max
-width:85%;
    max
-height:85%;
    overflow
:auto;
    padding
:10px;
    background
-color: white;
    border
:15px solid white;
    border
-radius:15px;
    padding
-top:10px;
}

div
[role="dialog"] .close {
    position
:absolute;
    top
: 2px;
    right
: 2px;
    border
:none;
    padding
:0;
    background
: white;
    cursor
:pointer;
}

JavaScript

function openDialog(dialogId,lastFocused){

    
var dialog = document.getElementById(dialogId);
    
var heading = dialog.querySelector("h1");
    
if(!lastFocused) var lastFocused = document.activeElement;

    dialog
.style.display = "block";
    heading
.focus();

    
function focusToDialog(ev){
        
var tmp = ev.target.parentNode;
        
while(tmp){
            
if(tmp == dialog) return;
            tmp
= tmp.parentNode;
        
}
        heading
.focus();
    
}
    document
.addEventListener("focus",focusToDialog,true);

    
var children = document.getElementsByTagName("body")[0].children;
    
var hiddens = [];
    
for(var i=0,el;el=children[i];i++){
        
if(el != dialog){

            
var ob = {};

            
if(el.hasAttribute("aria-hidden")){
                ob
.ariaHidden = el.getAttribute("aria-hidden");
            
}
            el
.setAttribute("aria-hidden","true");

            ob
.el = el;
            hiddens
.push(ob);
        
}
    
}

    
var closeButton = dialog.querySelector(".close");
    closeButton
.addEventListener("click",closeDialog);

    
function closeOnEscape(ev){
        
if(ev.key.slice(0,3) == "Esc") closeDialog();
    
}
    dialog
.addEventListener("keydown",closeOnEscape);

    dialog
.closeDialog = closeDialog;

    
function closeDialog(ev){

        document
.removeEventListener("focus",focusToDialog,true);
        closeButton
.removeEventListener("click",closeDialog);
        dialog
.removeEventListener("keydown",closeOnEscape);
        dialog
.closeDialog = null;
        closeButton
.removeEventListener("keydown",wrapFocusUp);
        topFocus
.removeEventListener("keydown",wrapFocusDown);

        
for(var i=0,ob;ob=hiddens[i];i++){
            
if(ob.ariaHidden){
                ob
.el.setAttribute("aria-hidden",ob.ariaHidden);
            
}else{
                ob
.el.removeAttribute("aria-hidden");
            
}
        
}

        lastFocused
.focus();
        dialog
.style.display = "none";
    
}

    
var topFocus = dialog.querySelector(".focus");
    
if(!topFocus) {topFocus = heading; heading.setAttribute("tabindex","0")};

    
function wrapFocusUp(ev){
        
if(ev.key == "Tab" && (!ev.shiftKey)){
            topFocus
.focus();
            ev
.preventDefault();
        
}
    
}
    closeButton
.addEventListener("keydown",wrapFocusUp);

    
function wrapFocusDown(ev){
        
if(ev.key == "Tab" && (ev.shiftKey)){
            closeButton
.focus();
            ev
.preventDefault();
        
}
    
}
    topFocus
.addEventListener("keydown",wrapFocusDown);
}

Notes

Ideally the dialog box content starts with a level 1 heading. Using a level 1 heading signals that this content differs from the main page content. A level 1 heading also allows for more nested heading levels in the dialog content.

Terms of Use

Developed by Pierre Frederiksen. Pierre is a Digital Access Specialist at Vision Australia

This software is being provided "as is", without any express or implied warranty. In particular, Vision Australia does not make any representation or warranty of any kind concerning the reliability, quality, or merchantability of this software or its fitness for any particular purpose. additionally, Vision Australia does not guarantee that use of this software will ensure the accessibility of your web content or that your web content will comply with any specific web accessibility standard.

Creative commons licence - logo
This work is licensed under a Creative Commons License













Print Print larger font