Button
Button
Overview
In most circumstances, using a native HTML <button>
or <input type="submit">
element is a better choice than creating a custom ARIA button, but there are use cases where an ARIA button can be appropriate, such as when styling a standard <button>
is too difficult in the range of target browsers under certain circumstances, or when retrofitting legacy markup that could be problematic to convert to standard <button>
elements.
Turn on a screen reader to experience this example in action.
Initial HTML Markup
<p>
<button class="deque-button" id="deque-button-primary-html">Primary Button (HTML)</button>
<span role="button" class="deque-button deque-button-aria" id="deque-button-primary-aria">Primary Button (ARIA)</span>
</p>
<p>
<button class="deque-button deque-button-secondary" id="deque-button-secondary-html" type="button">Secondary Button (HTML)</button>
<span role="button" class="deque-button deque-button-secondary deque-button-aria" id="deque-button-secondary-aria">Secondary Button (ARIA)</span>
</p>
JavaScript
Required: The complete JavaScript file (for all patterns in the library): deque-patterns.min.js
Key parts of the JavaScript file
Note: The code below is functional only in the context of the complete JavaScript file.
In the @button section:
function bindElementToInputs(element, behavior) {
if (element.tagName.toUpperCase() !== 'BUTTON') {
(0, _keyboardUtils.onElementSpace)(element, function (e) {
e.preventDefault();
e.stopPropagation();
behavior(e);
});
(0, _keyboardUtils.onElementEnter)(element, function (e) {
e.preventDefault();
e.stopPropagation();
behavior(e);
});
}
element.addEventListener('click', behavior);
}
function assignRoleIfNecessary(element) {
if (element.tagName.toLowerCase() !== 'button') {
element.setAttribute('role', 'button');
element.setAttribute('tabindex', '0');
}
}
function initializeIcon(element) {
var icon = document.createElement('span');
icon.setAttribute('aria-hidden', true);
icon.classList.add('pressed-on-icon');
element.insertBefore(icon, element.firstChild);
}
function initializeButton(element, behavior) {
assignRoleIfNecessary(element);
bindElementToInputs(element, behavior);
}
function initializeToggleButton(element, toggleFunction) {
var initialState = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
assignRoleIfNecessary(element);
element.setAttribute('aria-pressed', initialState);
initializeIcon(element);
if (!toggleFunction) {
throw new Error('You must provide a toggle function to a toggle button. It should return \'true\' when the button should have a \'pressed\' state, and \'false\' otherwise.');
}
function toggle(e) {
var toggledOn = toggleFunction(e);
element.setAttribute('aria-pressed', toggledOn ? 'true' : 'false');
}
bindElementToInputs(element, toggle);
}
In the @keyboardUtils section:
var KEYS = exports.KEYS = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
ESCAPE: 27,
SPACE: 32,
LEFT: 37,
RIGHT: 39,
UP: 38,
DOWN: 40,
F10: 121,
HOME: 36,
END: 35,
PAGE_UP: 33,
PAGE_DOWN: 34
};
function bindElementToEventValue(element, eventName, testValue, handler) {
function localHandler(e) {
if (e.which === testValue) {
handler(e);
}
}
element.addEventListener(eventName, localHandler);
return function () {
element.removeEventListener(eventName, localHandler);
};
}
function bindElementToKeypressValue(element, testValue, handler) {
return bindElementToEventValue(element, 'keypress', testValue, handler);
}
function bindElementToKeydownValue(element, testValue, handler) {
return bindElementToEventValue(element, 'keydown', testValue, handler);
}
function onElementEnter(element, handler) {
return bindElementToKeydownValue(element, KEYS.ENTER, handler);
}
function onElementEscape(element, handler) {
return bindElementToKeydownValue(element, KEYS.ESCAPE, handler);
}
function onElementSpace(element, handler) {
return bindElementToKeypressValue(element, KEYS.SPACE, handler);
}
function onElementLeft(element, handler) {
return bindElementToKeydownValue(element, KEYS.LEFT, handler);
}
function onElementRight(element, handler) {
return bindElementToKeydownValue(element, KEYS.RIGHT, handler);
}
function onElementUp(element, handler) {
return bindElementToKeydownValue(element, KEYS.UP, handler);
}
function onElementDown(element, handler) {
return bindElementToKeydownValue(element, KEYS.DOWN, handler);
}
function onElementHome(element, handler) {
return bindElementToKeydownValue(element, KEYS.HOME, handler);
}
function onElementEnd(element, handler) {
return bindElementToKeydownValue(element, KEYS.END, handler);
}
function onElementPageUp(element, handler) {
return bindElementToKeydownValue(element, KEYS.PAGE_UP, handler);
}
function onElementPageDown(element, handler) {
return bindElementToKeydownValue(element, KEYS.PAGE_DOWN, handler);
}
function onElementF10(element, handler) {
return bindElementToKeydownValue(element, KEYS.F10, handler);
}
function isAlphaNumeric(charCode) {
return charCode >= 48 && charCode <= 90 /* numbers, uppercase letters */
|| charCode >= 97 && charCode <= 122 /* lowercase letters */;
}
function onElementCharacter(element, handler) {
function localHandler(e) {
var charCode = e.which;
if (isAlphaNumeric(charCode)) {
handler(e);
}
}
element.addEventListener('keypress', localHandler);
return function () {
element.removeEventListener('keypress', localHandler);
};
}
function trapEnter(element) {
onElementEnter(element, function (e) {
e.stopPropagation();
e.preventDefault();
});
}
Required: Initialization JavaScript (with functionality specific to individual widget instances):
/* Specify which buttons */
var primaryHtml = document.getElementById('deque-button-primary-html');
var primaryAria = document.getElementById('deque-button-primary-aria');
var secondaryHtml = document.getElementById('deque-button-secondary-html');
var secondaryAria = document.getElementById('deque-button-secondary-aria');
/* Define the functionality for the buttons */
function primaryHtmlHandler() {
alert('You clicked the primary HTML button');
}
function primaryAriaHandler() {
alert('You clicked the primary ARIA button');
}
function secondaryHtmlHandler() {
alert('You clicked the secondary HTML button');
}
function secondaryAriaHandler() {
alert('You clicked the secondary ARIA button');
}
/* Initialize buttons */
deque.button.initializeButton(primaryHtml, primaryHtmlHandler);
deque.button.initializeButton(primaryAria, primaryAriaHandler);
deque.button.initializeButton(secondaryHtml, secondaryHtmlHandler);
deque.button.initializeButton(secondaryAria, secondaryAriaHandler);
CSS
Required: The complete CSS file (for all patterns in the library): deque-patterns.min.css
Key styles within the CSS file (other styles may also be necessary):
button.deque-button,
input[type='submit'].deque-button,
input[type='image'].deque-button,
input[type='reset'].deque-button,
[role='button'].deque-button {
cursor: default;
margin: 0;
font-size: 15px;
max-width: 374px;
min-width: 120px;
display: inline-block;
padding: 9px 12px 10px;
border: solid 1px #006cc1;
border-radius: 0;
overflow: hidden;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: bottom;
outline: none;
background: #006cc1;
color: #ffffff;
font-family: inherit;
}
button.deque-button:hover,
input[type='submit'].deque-button:hover,
input[type='image'].deque-button:hover,
input[type='reset'].deque-button:hover,
[role='button'].deque-button:hover {
background-color: #fdf6e7;
color: #004c87;
border-color: #990000;
margin: 0;
}
button.deque-button.deque-button-secondary,
input[type='submit'].deque-button.deque-button-secondary,
input[type='image'].deque-button.deque-button-secondary,
input[type='reset'].deque-button.deque-button-secondary,
[role='button'].deque-button.deque-button-secondary {
background-color: #e6e6e6;
color: #000000;
border: 1px solid #006cc1;
}
button.deque-button.deque-button-secondary:hover,
input[type='submit'].deque-button.deque-button-secondary:hover,
input[type='image'].deque-button.deque-button-secondary:hover,
input[type='reset'].deque-button.deque-button-secondary:hover,
[role='button'].deque-button.deque-button-secondary:hover {
background-color: #fdf6e7;
color: #004c87;
border-color: #990000;
border: 2px solid red;
}
button.deque-button:focus,
input[type='submit'].deque-button:focus,
input[type='image'].deque-button:focus,
input[type='reset'].deque-button:focus,
[role='button'].deque-button:focus {
outline: 1px dashed #000000;
}
Implementation Instructions
Step 1: Add Dependencies
Add deque-patterns.min.css in the <head>
of the document.
<link rel="stylesheet" type="text/css" href="deque-patterns.min.css">
Add a script link to deque-patterns.min.js to the bottom of the page.
<script type="text/javascript" src="deque-patterns.min.js"></script>
Step 2: Add HTML
- Wrap the HTML in
<div class="deque-wrapper">
, for styling purposes. - Identify the elements to be used as buttons with a unique ID. The element can be a
<span>
or any other element.
Note:
If your element is already a <button>
, the JavaScript simply wires it up to the handler but if it's a non-button element, role="button"
is set and bindings are created for space bar and enter key activation in addition to click.
Step 3: Add JavaScript
Call deque.button.initializeButton
with:
- The element you want to behave like a button
- A function you want executed when the button is activated (there is no default behavior; you have to write the function).