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.

Toggle Button (ARIA)

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:

  1. the element you want to behave like a button
  2. 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.