Button (Toggle)
Button (Toggle)
A toggle button is similar to a checkbox because it is either selected or not selected, and every toggle button can operate independent of all other toggle buttons. In fact, a checkbox can be an acceptable alternative to a toggle button.
Turn on a screen reader to experience this example in action.
Initial HTML Markup
<p>
<button class="deque-button deque-button-toggle" id="toggleHtml">Toggle Button (HTML)</button>
<span role="button" class="deque-button deque-button-toggle" id="toggleAria">Toggle 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 pattern instances):
var toggleHtml = document.getElementById('toggleHtml');
var toggleAria = document.getElementById('toggleAria');
function toggleHandler(e) {
var currentlyPressed = e.target.getAttribute('aria-pressed') === 'true';
return !currentlyPressed;
}
deque.button.initializeToggleButton(toggleHtml, toggleHandler);
deque.button.initializeToggleButton(toggleAria, toggleHandler);
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.deque-button-toggle,
[role='button'].deque-button.deque-button-toggle {
min-width: auto;
}
button.deque-button.deque-button-toggle[aria-pressed='true'],
[role='button'].deque-button.deque-button-toggle[aria-pressed='true'] {
background-color: #2f2f2f;
color: #ffffff;
border: 1px solid #000000;
}
button.deque-button.deque-button-toggle[aria-pressed='true']:hover,
[role='button'].deque-button.deque-button-toggle[aria-pressed='true']:hover {
background-color: #2f2f2f;
color: #ffffff;
}
button.deque-button.deque-button-toggle[aria-pressed='true']::before,
[role='button'].deque-button.deque-button-toggle[aria-pressed='true']::before {
font-family: 'mwf-glyphs';
content: '\E73E';
font-size: 12px;
font-weight: bold;
padding-right: 5px;
float: left;
}
.deque-button {
font-family: system-ui;
}
button.deque-button,
input[type='submit'].deque-button,
input[type='image'].deque-button,
input[type='reset'].deque-button,
[role='button'].deque-button {
white-space: normal;
}
Implementation Instructions
Step 1: Add Dependencies
CSS: Add deque-patterns.min.css in the <head>
of the document.
<link rel="stylesheet" type="text/css" href="deque-patterns.min.css">
JavaScript: 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>
Fonts: Font glyphs are required for this pattern (as currently styled). The CSS file refers to them in a folder called "_fonts" as a child element of the folder in which the CSS file resides. The fonts, from the Microsoft Web Framework (MWF) are available at these links:
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.
Step 3: Add JavaScript
Call deque.button.initializeToggleButton
with both of the following:
- the element you want to behave like a button
- the function you want executed when the button is activated
Note
Your function must return either true
or false
, because its return value determines the value of aria-pressed
on the button element.