Button

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.



Primary Button (ARIA)

Secondary Button (ARIA)

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 bindElementToKeypressValue(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;
}
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"></li>

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:

  1. The element you want to behave like a button
  2. A function you want executed when the button is activated (there is no default behavior; you have to write the function).