ARIA Dialog Example (Modal)
The dialog follows the best practice design patterns from Making a Dialog Modal. For example, The user can not tab out of the dialog. To close or leave the dialog, they need to press either the OK or the cancel button.
Step by Step: How we made the dialog (tutorial)
In the discussion on ARIA, we discussed 5 steps to making complex things accessible with ARIA.
Let's take a quick look at them again.
- Alert users to what each elements is: Their role (such as checkbox).
- Alert users to their properties and important relationships (such as disabled, required,and other labels).
- Alert users to what each element is doing: The state (such as checked).
- Alert users to changes in their state.
- Make sure widgets are keyboard accessible and focus works predictably. Events can be triggered though the keyboard, and it should be intuitive to the user. All controls should receive focus via tabbing though the keyboard.
Step 1: Set roles.
Our buttons are HTML standard form controls so we do not need form control roles as well. The Div that contained the dialog content however needs a role dialog as the tag "Div" does not describe how it behaves.
Step 2: Set properties and important relationships
We want to make sure the structure, relationships between elements and groups, properties and labels are all clear in the code. They can be clear from the standard HTML code, or using ARIA.
Firstly note the structure. The dialog contents are all rapped inside the dialog role so that they belong together is implied in the code or DOM. So the the groups are clear.
What about properties and labels?
The role dialog really describes the behavior quite well so there is little to add. The only property we need to set is to set the dialog header as it's label using aria-labelledby.
<div id="mydialog" aria-hidden="true" role="dialog" class="dialogclass" aria-labelledby="dialogheader">
<div class="whitebox">
<h1 id="dialogheader" tabindex="0" >login</h1>
Note that it is always worth looking at any requirments such as required states, and supported states for your roles before deciding what properties and states you need.
Step 3: Set the initial states
A good rule of thumb is elements that change how they look often have states that can change. The dialog changes in that it goes from being visible to invisible. So we are going to use aria-hidden="true" on the dialog initially and change it's value when it is shown.
We also use CSS selectors, to show and hide the dialog based on the aria-hidden value. Now I do not need to change their CSS class in the code, only the value of aria-hidden.
div[aria-hidden='true'] {
display: none;
}
A word about backward compatibility....
Unfortunately IE 9 does not support the CSS selectors above. (IE 10 does support them.) So to make this work with older browser I added a class hidden:
.hidden {display:none;}
I then added "hidden" to the class attribute in the HTML to hide the hidden items in older browsers.
class = "... hidden"
Step 4: Set changes in state.
We discussed above that the dialog uses the aria state of aria-hidden, and we are using CSS selectors so that whenever aria-hidden='true'
the dialog will be hidden and when aria-hidden='false'
the dialog will be visible.
In the code we have a function to open the dialog by changing the values of aria-hidden.
$("div#mydialog").attr("aria-hidden","false");
For older browsers, whenever I set aria-hidden = "true"
in the script, I also added the class hidden to the same selector:
$("....").addClass("hidden");
I also removed it when I set aria-hidden = "false"
:
$("...").removeClass("hidden");
This makes the script much more robust in older browsers.
Step 4: Manage focus and keyboard accessibility.
Both the focus and keyboard accessibility needs to be managed. We also need to make the dialog modal - so that users stay in the dialog until they actively close it.
Firstly lets deal with the keyboard accessibility and make sure our jQuery selectors are device independent. In the code we have used mouse events such as "click()" to trigger the button function such as : "$("input#login").click(function()"
We need to add device independent events as well for people who can not use a mouse.
The following function gives keyboard accessibility for all the buttons, so that pressing enter when a button has focus (key-code 13) is the same as clicking the button with a mouse.
$("input[type='button']").keydown(function(ev) {
if (ev.which ==13) {
$(this).click()
}
});
Next we need to think about focus. For this design pattern a few things have to happen. Firstly the dialog should get focus when it is opened. Also, as the dialog is modal we want the rest of the page content (inside div main1) to be removed from the tabbing order. We do that by setting the tabindex of the main content to -1. Altogether the code to open the dialog looks like this:
$("input#login").click(function(){
$("div#mydialog").attr("aria-hidden","false");
$("div#main1").attr("tabindex","-1");
$("#dialogheader").focus();
});
We also want to trap the focus so that when the dialog is open the user will not be able to just tab out of the dialog into the main content. They need to close the dialog in order to leave it. For example, when the user is on the the first item in the dialog, pressing shift-alt will take them to the last element in the dialog.
$("#dialogheader").keydown(function(ev){
if (ev.shiftKey && ev.which == 9) {
$("#ok").focus();
ev.preventDefault();
}
});
Another aspect of this design pattern is that the user can also close the dialog by pressing the esc key.
if (ev.which == 27) {
$("input#cancel").click();}
Finally we need to manage the focus when the user closes the dialog. This involves, hiding the dialog, removing it from the tabbing order (tabindex = -1) putting the main content back in the tabbing order (tabindex = 0) and returning the focus to where it was before we opened the dialog.
$("input#cancel").click(function(){
$("div#mydialog").attr("aria-hidden","true");
$("div#main1").attr("tabindex","0");
$("input#login").focus() ;
ev.preventDefault();
});