Using ARIA role=dialog to implement a modal dialog box

From WCAG WG


Status

Editorial note

  • The title of this technique is just a draft - it may become just an example in a more encompassing technique (e.g., Using ARIA role=dialog).
  • Marc and Loretta changed the title to "Using ARIA role=dialog to implement a model dialog box".

User Agent Notes [To be published separately]

Screen reader support is decent on desktop, still patchy in mobile OSs and browsers (iOS/Safari/VoiceOver, Android/Firefox/Talkback)

  • Win 7, IE 9, JAWS 14: supported
  • Win 7, FF 18, JAWS 14: not supported
  • Win 7, IE9, NVDA 2012.2.1 supported
  • Win 7, FF 18, NVDA 2012.2.1: supported
  • Mac OS 10.8.X, Safari, VoiceOver: supported
  • iOS 6.1,Safari, VoiceOver (iPad mini): partial support (does not annoince role dialog, but the dialog heading associated via aria-labelledby)
  • Android 4.2, Firefox, Talkback (Nexus 7): partial support (dialog is announced, but only after closing the dialog)

Full testing results at Using Aria role=dialog (optimised for mobile browsers), now also tested with NVDA 2012.3.1

Applicability

As of this writing this technique applies to HTML technologies.

This technique relates to:

Description

Authors often use JavaScript to insert custom modal dialogs (usually divs) instead of opening a new browser window. The ARIA role dialog can be used on elements used as containers for the dialog to inform screen reader users that a custom dialog is being inserted (or made visible via CSS).

In order to ensure proper focus handling on mobile devices it is also advisable to mask the page background (i.e. all page elements except the content of the modal custom dialog) by setting its aria-hidden attribute to true once the dialog is displayed (and back to false when the dialog is closed). Note that this masking should happen after the script has moved the focus to the dialog.

Note on focus management

When using custom dialogs, it is vital that authors use scripting to move the focus to the displayed dialog (and, once it is closed, back to the trigger that brought it up). This is especially important as most dialogs are inserted far from the point in the source code that triggers them. For example, the dialog code may be appended at the end of the page's source code. Without proper focus management through scripting, keyboard users may find it hard or impossible to identify and interact with the custom dialog.

Scripted focus management should therefore

  1. move the keyboard focus from the triggering element (link or button) to the custom dialog (or one of the focussable elements within it)
  2. keep it within the custom dialog until the dialog is closed (this makes the dialog effectively modal)
  3. move it back to the triggering element when the user closes the dialog by activating one of the choices offered.

Examples

Example 1: A button opens a custom dialog

A button on the page ("Display a dialog") displays a small custom dialog with a text ("Just an example") and two buttons, "OK" and "Cancel". In the example below, the script merely shows a hidden div. More frequently, the dialog will be inserted into the page via AJAX / DOM scripting.

<p><a onclick="toggleDialog('show');" href="#">Display a dialog</a></p>

<div tabindex="-1" style="display: none;" role="dialog" aria-labelledby="myDialog" id="box" class="box-hidden">
	<h3 id="myDialog">Just an example.</h3>
	<button onclick="toggleDialog('hide');" class="close-button">OK</button>
	<button onclick="toggleDialog('hide');" class="close-button">Cancel</button>		
</div>

Style and Script in <head> section of page:

<style>
.box-hidden {
	display: none;
	position: absolute;
	top: 19em; left:15em; width:20em; height:5em;
	border: 2px solid black;
	padding:0 1em 1em 1em;
	background-color: #eee;
	z-index:1002;
	overflow: auto;
	}		
</style>

<script>
var dialogOpen = false, lastFocus, dialog, okbutton, pagebackground;

function showDialog(el) {
	lastFocus = el || document.activeElement;
	toggleDialog('show');
}
function hideDialog(el) {
	toggleDialog('hide');
}

function toggleDialog(sh) {
	dialog = document.getElementById("box");
	okbutton = document.getElementById("ok");
	pagebackground = document.getElementById("bg");

	if (sh == "show") {
		dialogOpen = true;

		// show the dialog 
		dialog.style.display = 'block';
		
		// after displaying the dialog, focus an element inside it
		okbutton.focus();
		
		// only hide the background *after* you've moved focus out of the content that will be "hidden"
		pagebackground.setAttribute("aria-hidden","true");
		
	} else {
		dialogOpen = false;
		dialog.style.display = 'none';
		pagebackground.setAttribute("aria-hidden","false");
		lastFocus.focus(); 
	}
}


document.addEventListener("focus", function(event) {

    var d = document.getElementById("box");

    if (dialogOpen && !d.contains(event.target)) {
        event.stopPropagation();
        d.focus();
    }

}, true);


document.addEventListener("keydown", function(event) {
    if (dialogOpen && event.keyCode == 27) {
        toggleDialog('hide');
    }
}, true);

</script>


Look at script of custom dialog (code only)

Resources

Related Techniques

Tests

Procedure

For elements that use ARIA role=dialog to implement a modal dialog:

  1. Check that role=dialog is an attribute of the container (such as a div) that is used as the custom dialog
  2. Check that the container is inserted (or made visible) via JavaScript following a user interaction or some other event
  3. When the dialog is activated, check that focus is set to an element in the container.
  4. When the dialog is active, check that focus is never set to an element that is not in the container.
  5. When the dialog is deactivated, check that focus is set to the control that originally activated the dialog.

Expected Results

  • All checks are true.

If this is a sufficient technique for a success criterion, failing this test procedure does not necessarily mean that the success criterion has not been satisfied in some other way, only that this technique has not been successfully implemented and can not be used to claim conformance.