Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active August 31, 2017 17:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/31939cd9fd3d00be68ffd5ab42622a35 to your computer and use it in GitHub Desktop.
Save dfkaye/31939cd9fd3d00be68ffd5ab42622a35 to your computer and use it in GitHub Desktop.
wai-aria-alertdialog per w3c best practice spec at https://www.w3.org/TR/wai-aria-practices/#alertdialog
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="HandheldFriendly" content="true" />
<title>WAI-ARIA Alert Dialog (Modal)</title>
<style>
:root {
font-size: 100%;
font-family: verdana, sans-serif;
line-height: 1.6;
margin: 1em;
padding: 1em;
}
html {
box-sizing: border-box;
}
body {
background: #eee;
}
*, *:before, *:after {
box-sizing: inherit;
}
body * + * {
margin: 1.5rem, 1.25rem, 1.25rem;
}
[aria-hidden="true"] {
display: none;
}
ul {
margin: 0 0 1rem 0;
padding: 0;
}
li {
margin: 0 0 0 1rem;
padding: 0;
}
abbr {
border-bottom: 1px dotted blue;
text-decoration: none;
}
kbd {
font-size: 1rem;
}
/** alertdialog rules **/
[role="alertdialog"] {
background: #fff;
border: 1px solid #ccc;
border-radius: 0.25em;
padding: 1.5em 2em 2em;
position: fixed;
/* pseudo-responsive settings */
bottom: 10%;
left: 10%;
right: 10%;
top: 10%;
}
/**
* Hide all subsequent siblings of alertdialog, which is a direct child of
* the body element.
*/
[open] ~ * {
visibility: hidden;
}
</style>
</head>
<body>
<dialog role="alertdialog" aria-hidden="true" aria-labelledby="dialogue-label">
<!-- We make the alertdialog a direct child of the body, so we can hide its
siblings when opened. The "dialogue-label" element is generated when
the user opens the dialog, and placed inside the dialog itself. -->
</dialog>
<header role="banner">
<h1>
<abbr title="Web Accessibility Initiative">WAI</abbr><span>&ndash;</span><abbr title="Accessible Rich Internet Applications">ARIA</abbr>
Alert Dialog (Modal)</h1>
<p>
&hellip; per <abbr title="World-Wide Web Consortium">W3C</abbr> best
practice spec at
<a href="https://www.w3.org/TR/wai-aria-practices/#alertdialog">
https://www.w3.org/TR/wai-aria-practices/#alertdialog</a>.
</p>
</header>
<main role="main">
<section>
<h2>Alert Dialog Behavior</h2>
<p>
Focus is maintained in the dialog while it is open, i.e., the
<kbd>Tab</kbd> key is cancelled when user tab's out of the last
element (button) in the dialog.
</p>
<p>
Focus is returned to the element (button) that initiated the dialog.
</p>
<p>
To open the dialog, navigate to the <kbd>Open</kbd> button, then
either click it, or press the <kbd>Space</kbd> bar or
<kbd>Enter</kbd> key.
</p>
<p>
To close the dialog, either press <kbd>Escape</kbd> key, or navigate
to the <kbd>Close</kbd> button, then either click it, or press the
<kbd>Space</kbd> bar or <kbd>Enter</kbd> key.
</p>
<button type="button" data-dialog-message="Click button to close the dialog">
Open
</button>
</section>
</main>
<footer role="contentinfo">
&copy; 2017
</footer>
<script>
(function() {
"use strict";
var dialog = document.querySelector('[role="alertdialog"]');
var handleClick = function(e) {
var target = e.target;
var message = target.getAttribute('data-dialog-message');
if (message) {
handleOpen(target, message);
}
};
var handleClose = function() {
dialog.parentNode.removeEventListener('keydown', handleEscape);
dialog.removeAttribute('open');
dialog.setAttribute('aria-hidden', 'true');
[].slice.call(dialog.querySelectorAll('button')).map(function(button) {
button.removeEventListener('click', handleClick);
button.removeEventListener('click', handleRefocus);
});
while (dialog.firstChild) {
dialog.removeChild(dialog.firstChild);
}
dialog.opener.focus();
dialog.opener = undefined;
};
var handleEscape = function(e) {
var k = e.which || e.keyCode;
if (k == '27') {
handleClose();
}
};
var handleOpen = function(opener, message) {
var content = document.createElement('div');
content.setAttribute('role', 'document');
content.setAttribute('tabindex', '0');
var labelId = dialog.getAttribute('aria-labelledby');
var label = document.createElement('p');
label.textContent = message;
label.setAttribute('id', labelId);
var button = document.createElement('button');
// this might be redundant
button.setAttribute('role', 'button');
// this is a safeguard against embedding in a form we don't want to submit
button.setAttribute('type', 'button');
button.textContent = 'Close';
button.addEventListener('click', handleClose);
button.addEventListener('keydown', handleRefocus);
content.appendChild(label);
content.appendChild(button);
dialog.appendChild(content);
dialog.opener = opener;
dialog.setAttribute('open', 'true');
dialog.setAttribute('aria-hidden', 'false');
content.focus();
// let parent listen to the ESC key
dialog.parentNode.addEventListener('keydown', handleEscape);
};
var handleRefocus = function(e) {
var k = e.which || e.keyCode;
// refocus on the dialog label if user tries to tab out
if (k == '9') {
dialog.querySelector('[tabindex="0"]').focus();
e.preventDefault();
}
};
document.addEventListener('DOMContentLoaded', function() {
var buttons = [].slice.call(document.querySelectorAll('[data-dialog-message]'));
buttons.map(function(button) {
button.addEventListener('click', handleClick);
});
});
}());
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment