Skip to content

Instantly share code, notes, and snippets.

@kloots
Created February 23, 2012 22:43
Show Gist options
  • Save kloots/1895502 to your computer and use it in GitHub Desktop.
Save kloots/1895502 to your computer and use it in GitHub Desktop.
ARIA Widget Examples Using jQuery
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="author" content="Todd Kloots">
<title>Registration Form</title>
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.4.1/build/cssreset/reset-min.css">
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.4.1/build/cssfonts/fonts-min.css">
<style type="text/css">
h1 {
margin: 1em;
font-weight: bold;
}
#personal-info {
margin: 1em;
padding: 1em;
border: solid 1px #000;
}
#personal-info label {
width: 10em;
display: inline-block;
text-align: right;
}
#errors {
color: #B70000;
font-weight: bold;
}
.error {
margin-left: 10.5em;
color: #B70000;
font-weight: bold;
}
.invalid {
outline: solid 1px #B70000;
}
#personal-info input[type=text],
#personal-info textarea {
border: solid 1px #999;
width: 15em;
}
#personal-info legend {
font-weight: bold;
}
#personal-info legend span {
display: block;
padding: 0 .25em;
}
#personal-info div {
margin: .25em 0;
}
#personal-info textarea {
height: 3em;
}
#personal-info fieldset {
background-color: #ccc;
padding: .25em;
border: solid 1px #000;
}
#personal-info #gender,
#personal-info #birthday {
border: 0;
}
#gender legend,
#birthday legend {
margin: 0 0 0 -.25em;
font-weight: normal;
}
#gender legend span,
#birthday legend span{
width: 10em;
display: inline-block;
text-align: right;
}
#gender label,
#birthday label {
display: inline;
width: auto;
}
#gender div,
#birthday div {
margin: -1.5em 0 0 10.25em;
}
#birthday div label {
position: absolute;
left: -999em;
}
#personal-info #birth-year {
width: 4em;
}
#preferences label {
display: inline;
}
#preferences span {
width: 10em;
display: inline-block;
text-align: right;
}
#preferences label#interests-lbl {
width: 10em;
display: inline-block;
vertical-align: top;
}
#preferences legend span {
width: auto;
text-align: left;
}
.dialog {
position: absolute;
-moz-box-shadow: 3px 3px 3px rgba(0,0,0,0.2), -3px 3px 3px rgba(0,0,0,0.2);
-webkit-box-shadow: 3px 3px 3px rgba(0,0,0,0.2), -3px 3px 3px rgba(0,0,0,0.2);
box-shadow: 3px 3px 3px rgba(0,0,0,0.2), -3px 3px 3px rgba(0,0,0,0.2);
}
.dialog fieldset {
border: solid 1px #000;
background-color: #fff;
}
.dialog legend {
display: block;
width: 100%;
}
.dialog legend span {
background-color: #ccc;
display: block;
border: solid 1px #000;
margin: 0 -1px;
font-weight: bold;
padding: 3px 6px;
}
.dialog .bd {
padding: 6px;
}
.dialog .ft {
padding: 6px;
}
.dialog.hidden {
display: none;
}
.modal-visible {
overflow: hidden;
}
.modal-mask {
position: absolute;
overflow: hidden;
background-color: #000;
opacity: .5;
filter: alpha(opacity=50);
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
}
</style>
</head>
<body>
<div id="reg-form" aria-hidden="false">
<h1>Registration Form</h1>
<form id="personal-info" live="polite" relevant="additions">
<fieldset>
<legend class="title"><span>Personal Information</span></legend>
<div><label for="first-name">First Name</label> <input type="text" name="first-name" id="first-name" aria-required="true" required></div>
<div><label for="last-name">Last Name</label> <input type="text" name="last-name" id="last-name" aria-required="true" required></div>
<fieldset id="gender">
<legend><span>Gender</span></legend>
<div>
<label for="gender-male">Male</label> <input type="radio" name="gender" id="gender-male" checked>
<label for="gender-female">Female</label> <input type="radio" name="gender" id="gender-female">
</div>
</fieldset>
<fieldset id="birthday">
<legend><span>Birthday</span></legend>
<div>
<label for="birth-month">Month</label>
<select id="birth-month" name="birth-month">
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6">June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
<label for="birth-day">Day</label>
<select id="birth-day" name="birth-day">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="18">18</option>
<option value="19">19</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
<option value="28">28</option>
<option value="29">29</option>
<option value="30">30</option>
<option value="31">31</option>
</select>
<label for="birth-year">Year</label>
<input type="text" id="birth-year" name="birth-year">
</div>
</fieldset>
</fieldset>
<fieldset class="section">
<legend><span>Home</span></legend>
<div><label for="home-address">Address</label> <textarea id="home-address" name="home-address"></textarea></div>
<div><label for="home-city">City</label> <input type="text" id="home-city" name="home-city"></div>
<div><label for="home-state">State</label> <input type="text" id="home-state" name="home-state"></div>
<div><label for="postal-code">Postal Code</label> <input type="text" id="postal-code" name="postal-code"></div>
</fieldset>
<fieldset class="section">
<legend><span>Contact Information</span></legend>
<div><label for="email">Email</label> <input type="email" id="email" name="email" aria-required="true" required></div>
<div><label for="phone">Phone</label> <input type="tel" id="phone" name="phone"></div>
</fieldset>
<div id="toolbar">
<input type="button" id="signup" value="Submit">
<input type="button" id="cancel" value="Cancel">
</div>
</form>
</div>
<div class="dialog hidden" role="alertdialog" aria-labelledby="dialog-label" aria-describedby="msg">
<form>
<fieldset>
<legend class="hd"><span id="dialog-label">Confirm Action</span></legend>
<div class="bd" id="msg">
<p>Are you sure you want to submit this form?</p>
</div>
<div class="ft">
<input type="button" id="ok-button" value="OK">
<input type="button" id="cancel-button" value="Cancel">
</div>
</fieldset>
</form>
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
(function ($) {
function createModalMask() {
var doc = document,
docEl = $.browser.msie && doc.compatMode ? doc.documentElement : doc.body;
$(docEl).addClass("modal-visible");
$('<div class="modal-mask"></div>').insertBefore('.dialog');
sizeModalMask();
$("#reg-form").attr("aria-hidden", true);
}
function removeModalMask() {
$('.modal-mask').remove();
$("#reg-form").attr("aria-hidden", false);
}
function sizeModalMask() {
var modalMask = $('.modal-mask'),
win = $(win);
modalMask.css({
width: win.width(),
height: win.height()
});
}
function positionDialog() {
var win = $(window);
dialog.css({
top: ((win.height()/2) - (dialog.height()/2)),
left: ((win.width()/2) - (dialog.width()/2))
});
}
function enforceModality(e) {
if (!$.contains(dialog[0], e.target)) {
dialog.find('#ok-button').focus();
}
}
var dialog = $(".dialog");
function hideDialog(activeEl) {
dialog.addClass("hidden");
removeModalMask();
dialog.off(".dialog");
$(window).off(".dialog");
activeEl.focus();
}
function showDialog() {
var activeEl = document.activeElement;
dialog.removeClass("hidden");
createModalMask();
positionDialog();
$('#ok-button').focus();
$(document).on("focusin.dialog", enforceModality);
$(window).on('resize.dialog', function () {
sizeModalMask();
positionDialog();
});
dialog.on("click.dialog", "input", activeEl, hideDialog);
dialog.on("keydown.dialog", function (e) {
var keyCode = e.keyCode;
if (keyCode === 27) { // Esc
hideDialog(activeEl);
}
// Handle Tab out from "Cancel" button
// This is necessary because the dialog is the last element in the DOM, making the
// "Cancel" button the last focusable element in the DOM. When the user presses Tab
// focus goes to the browser chrome, but we don't get a focus event. Further,
// when focus moves to the browser chrome, Firefox doesn't clear document.activeElement
// meaning you cannot detech that the the dialog has lost focus and direct focus back to it.
else if (keyCode === 9 && !e.shiftKey && e.target === $("#cancel-button")[0]) {
e.preventDefault();
}
});
}
function showErrors(fields) {
fields = $(fields);
fields.each(function () {
var field = $(this),
invalid = field.hasClass("invalid"),
descID = this.id + "-desc";
if (field.val().length === 0 && !invalid) {
field.addClass("invalid").attr({
"aria-invalid": true,
"aria-describedby": descID
}).after('<p class="error" id="' + descID + '">' + $("label[for=" + this.id + "]").text() + ' cannot be blank.</p>');
}
});
if ($(".error").length > 0 && !$("#errors")[0]) {
$("#personal-info").prepend('<div id="errors" role="alert">This form has one or more errors</div>');
}
}
function clearErrors(fields) {
fields = $(fields);
fields.each(function () {
var field = $(this);
if (field.val().length > 0) {
field.removeClass("invalid").attr("aria-invalid", false);
field.parent().find(".error").remove();
}
});
var errorSummary = $("#errors");
if ($(".error").length === 0 && errorSummary[0]) {
errorSummary.remove();
}
}
var regForm = $("#personal-info");
regForm.on("blur", "input[required]", function () {
showErrors("#" + this.id);
});
regForm.on("blur", "input.invalid", function () {
clearErrors("#" + this.id);
});
$("#signup").on("click", function () {
showErrors("input[required]");
if ($(".error").length === 0) {
showDialog();
}
else {
$("input.invalid")[0].focus();
}
});
}(jQuery.noConflict()));
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="author" content="Todd Kloots">
<title>ListBox Test Page</title>
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.4.1/build/cssfonts/cssfonts-min.css">
<style type="text/css">
ul {
list-style-type: none;
margin: 2px 0 0 0;
padding: 0;
border: 2px inset #ccc;
width: 176px;
}
li.selected {
background: #ccc;
}
li:focus {
outline: none;
}
ul.focus {
border-color: #3875D7;
}
ul.focus li.selected {
background: #3875D7;
color: #fff;
}
</style>
</head>
<body>
<ul id="listbox" role="listbox" aria-label="Interests">
<li role="option" tabindex="0">Activism</li>
<li role="option" tabindex="-1">Baking</li>
<li role="option" tabindex="-1">Cooking</li>
<li role="option" tabindex="-1">Dancing</li>
<li role="option" tabindex="-1">Fine Art</li>
<li role="option" tabindex="-1">Ice Skating</li>
<li role="option" tabindex="-1">Music</li>
<li role="option" tabindex="-1">Politics</li>
<li role="option" tabindex="-1">Sports</li>
<li role="option" tabindex="-1">Travel</li>
<li role="option" tabindex="-1">Technology</li>
</ul>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
(function ($) {
var listbox = $("#listbox");
function makeSelection(li) {
listbox.find("li.selected").attr({
'tabIndex': -1,
'aria-selected': false
}).removeClass("selected");
li.attr({
'tabIndex': 0,
'aria-selected': true
}).addClass("selected");
}
listbox.on("mousedown", "li", function () {
makeSelection($(this));
});
listbox.on("keydown", "li", function (e) {
var keyCode = e.keyCode,
li = $(this),
next,
nextItem;
if (keyCode === 38) { // UP
next = "prev";
}
else if (keyCode === 40) { // DOWN
next = "next";
}
if (next) {
// On initial arrow key press, don't advance focus,
// just synchronize focus and selection
if (!li.hasClass('selected')) {
li.addClass("selected").attr('aria-selected', true);
}
else if ((nextItem = li[next]()) && nextItem[0]) {
makeSelection(nextItem);
nextItem.focus();
}
}
});
listbox.on("focusin", function (e) {
if (!listbox.hasClass("focus")) {
listbox.addClass("focus");
}
});
listbox.on("focusout", function (e) {
setTimeout(function () {
if (!$.contains(listbox[0], document.activeElement)) {
listbox.removeClass("focus");
}
}, 0);
});
}(jQuery.noConflict()));
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="author" content="Todd Kloots">
<title>MenuButton Example</title>
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.4.1/build/cssfonts/cssfonts-min.css">
<style type="text/css">
.menubutton {
overflow: visible;
border: solid 1px #999;
border-radius: 2px;
margin: 0;
padding: 2px 20px 2px 4px;
background: url(assets/menu-button-arrow.png) right center no-repeat;
}
.menu {
border: solid 1px #808080;
background: #fff;
position: absolute;
list-style-type: none;
margin: 0;
padding: 3px 0;
-moz-box-shadow: 3px 3px 3px rgba(0,0,0,0.2), -3px 3px 3px rgba(0,0,0,0.2);
-webkit-box-shadow: 3px 3px 3px rgba(0,0,0,0.2), -3px 3px 3px rgba(0,0,0,0.2);
box-shadow: 3px 3px 3px rgba(0,0,0,0.2), -3px 3px 3px rgba(0,0,0,0.2);
}
.menu:focus {
outline: none;
}
.menu.hidden {
display: none;
}
.menu li {
padding: 3px 20px;
}
.menu li.selected {
background: #3875D7;
color: #fff;
}
.menu li:focus {
outline: none;
}
</style>
</head>
<body>
<button role="button" class="menubutton" id="move-btn" type="button" aria-haspopup="true">Move To</button>
<ul class="menu hidden" aria-hidden="true" role="menu" aria-label="folders" tabindex="-1">
<li role="menuitem" tabindex="-1">Inbox</li>
<li role="menuitem" tabindex="-1">Archive</li>
<li role="menuitem" tabindex="-1">Trash</li>
</ul>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
(function ($) {
var button = $("#move-btn"),
menu = $(".menu");
function showMenu() {
menu.find(".selected").removeClass("selected");
menu.removeClass("hidden");
menu.attr("aria-hidden", false);
var position = button.position();
menu.css({
top: (position.top + button.outerHeight()),
left: position.left
});
menu.focus();
}
button.on({
keydown: function (e) {
var keyCode = e.keyCode;
if (keyCode === 13 || keyCode === 32) { // Enter and Space
showMenu();
}
},
mousedown: function (e) {
e.preventDefault();
showMenu();
}
});
menu.on("mouseenter", "li", function () {
menu.find(".selected").removeClass("selected");
$(this).addClass("selected");
});
menu.on("mouseleave", "li", function () {
menu.find(".selected").removeClass("selected");
});
menu.on("keydown", function (e) {
var keyCode = e.keyCode,
target = e.target,
selected,
selector,
event;
switch (keyCode) {
case 27:
button.focus();
break;
case 38:
case 40:
if (target === menu[0]) {
if ((selected = menu.find(".selected")) && selected[0]) {
event = $.Event("keydown", { keyCode: keyCode });
selected.trigger(event);
}
else {
if (keyCode === 38) { // UP
selector = "li:last-child";
}
else if (keyCode === 40) { // DOWN
selector = "li:first-child";
}
menu.find(selector).addClass("selected").focus();
}
}
break;
}
});
menu.on("click", "li", function (e) {
console.log("You clicked: " + $(e.target).text());
});
menu.on("keydown", "li", function (e) {
var selected = menu.find(".selected"),
li = selected[0] ? selected : $(e.target),
next,
nextItem;
switch (e.keyCode) {
case 38: // UP
next = "prev";
break;
case 40: // Down
next = "next";
break;
case 13: // Enter
case 32: // Space
selected.trigger($.Event("click"));
selected.blur();
break;
}
if (next && (nextItem = li[next]()) && nextItem[0]) {
li.removeClass("selected");
nextItem.addClass("selected").focus();
}
});
menu.on("focusout", function (e) {
setTimeout(function () {
if (!$.contains(menu[0], document.activeElement)) {
menu.addClass("hidden");
menu.attr("aria-hidden", true);
}
}, 0);
});
}(jQuery.noConflict()));
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="author" content="Todd Kloots">
<title>Toolbar Example</title>
<style type="text/css">
.toolbar {
border: 1px solid #666;
background: #ddd;
padding: 3px;
font-size: 0;
float: left;
}
.toolbar button {
float: left;
border: 1px solid #333;
background: #bbb;
padding: 0;
margin: 0 3px 0 0;
width: 36px;
height: 36px;
overflow: hidden;
}
/* Remove the extra padding and border given to buttons by
default in Firefox to ensure correct alignment of the img. */
.toolbar button::-moz-focus-inner {
border: 0;
padding: 0;
}
/* Hide the text label by inserting an image before it. */
.toolbar button:before {
display: inline-block;
content: url('assets/toolbar_icon_set_full.png');
}
.toolbar button {
*background: url('assets/toolbar_icon_set_full.png') no-repeat;
*text-indent: 36px;
}
.toolbar button.focus,
.toolbar button:focus {
outline: none;
background-color: #3875D7;
}
.toolbar .prnt {
*background-position: -38px -74px;
}
.toolbar .prnt:before {
margin: -73px 0 0 -37px;
}
.toolbar .find {
*background-position: -182px -146px;
}
.toolbar .find:before {
margin: -145px 0 0 -181px;
}
.toolbar .save {
*background-position: -146px -74px;
}
.toolbar .save:before {
margin: -73px 0 0 -145px;
}
.toolbar .sets {
*background-position: -74px -110px;
}
.toolbar .sets:before {
margin: -109px 0 0 -73px;
}
.toolbar .info {
*background-position: -146px -146px;
}
.toolbar .info:before {
margin: -145px 0 0 -145px;
}
</style>
</head>
<body>
<div role="toolbar" class="toolbar">
<button type="button" tabindex="0" class="prnt">Print</button>
<button type="button" tabindex="-1" class="find">Find</button>
<button type="button" tabindex="-1" class="save">Save</button>
<button type="button" tabindex="-1" class="sets">Settings</button>
<button type="button" tabindex="-1" class="info">Info</button>
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
(function ($) {
var toolbar = $(".toolbar"),
buttons = toolbar.find("button"),
browser = $.browser;
buttons.attr("tabIndex", -1);
buttons.first().attr("tabIndex", 0);
if (browser.msie && browser.version < 8) {
toolbar.on("focus", "button", function () {
$(this).addClass("focus");
});
toolbar.on("blur", "button", function () {
$(this).removeClass("focus");
});
}
toolbar.on("keydown", "button", function (e) {
var keyCode = e.keyCode,
button = $(this),
next;
if (keyCode === 37) { // Left
next = "prev";
}
else if (keyCode === 39) { // Right
next = "next";
}
if (next) {
button[next]().focus();
}
});
}(jQuery.noConflict()));
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment