Link

Even though it is always best to use standard HTML links whenever possible, on rare occasions it can be acceptable to create ARIA links. The ARIA links need to act exactly like standard links.



Example 1: Regular HTML link (no JavaScript or ARIA)
DequeUniversity.com

Example 2: ARIA link (<span role="link">), opening in a new window/tab
Deque.com

Example 3: ARIA image link (<img role="link">), opening in a new window/tab
Deque

Initial HTML Markup

Example 1: Regular HTML link (no JavaScript or ARIA)

<a class="deque-link" href="https://dequeuniversity.com">DequeUniversity.com</a>

Example 2: ARIA link (<span role="link">), opening in a new window/tab

<span role="link" id="deque-link-aria" class="deque-link  deque-link-aria" data-href="http://www.deque.com" data-target="_blank">Deque.com</span>

Example 3: ARIA image link (<img role="link">), opening in a new window/tab

<img role="link" id="deque-link-aria-image" class="deque-link deque-link-aria" src="assets/js/patterns/images/DequeLogo.png" width="150" style="background: #555555" data-href="https://www.dequeuniversity.com" data-target="_blank" alt="Deque">

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 @link section:


	function initializeLink(element) {
	
	  if (element.tagName.toLowerCase() === 'a') {
	    return;
	  }
	  var targetURL;
	  element.classList.add('deque');
	
	  if (element.getAttribute('href')) {
	    targetURL = element.getAttribute('href');
	  } else if (element.getAttribute('data-href')) {
	    targetURL = element.getAttribute('data-href');
	  } else {
	    return;
	  }
	
	  function navigateToLink(e) {
	    e.preventDefault();
	    e.stopPropagation();
	    var dataTarget = element.getAttribute('data-target');
	    if (dataTarget) {
	      window.open(targetURL, dataTarget);
	    } else {
	      window.location.href = targetURL;
	    }
	  }
	
	  element.setAttribute('tabindex', 0);
	  element.setAttribute('role', 'link');
	  element.addEventListener('click', navigateToLink);
	  (0, _keyboardUtils.onElementEnter)(element, navigateToLink);
	  (0, _keyboardUtils.onElementF10)(element, function (e) {
	    if (e.shiftKey) {
	      if (window.CustomEvent) {
	        try {
	          element.dispatchEvent(new CustomEvent('contextmenu'));
	        } catch (e) {
	          return;
	        }
	      } else if (document.createEvent) {
	        var ev = document.createEvent('HTMLEvents');
	        ev.initEvent('contextmenu', true, false);
	        element.dispatchEvent(ev);
	      } else {
	        // Internet Explorer
	        element.fireEvent('oncontextmenu');
	      }
	    }
	  });
	}
	
	function initializeAllLinks() {
	  var links = document.querySelectorAll('.deque-link-aria');
	  for (var i = 0; i < links.length; i++) {
	    initializeLink(links[i]);
	  }
	}
	
	initializeAllLinks();
	

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();
	  });
	}
	

Note: No additional JavaScript initialization code is necessary for this pattern. All elements with class="deque-link-aria" will be initialized automatically by the external JavaScript file.

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):


.deque[role='link'].deque-link,
a.deque-link {
  color: #006cc1;
  text-decoration: underline;
}
.deque[role='link'].deque-link:hover,
a.deque-link:hover {
  cursor: pointer;
  color: rgba(0, 0, 0, 0.8);
  text-decoration: none;
  outline: 1px dashed #000000;
}
.deque[role='link'].deque-link:focus,
a.deque-link:focus {
  outline: 1px dashed #000000;
  color: rgba(0, 0, 0, 0.8);
  text-decoration: none;
}
.deque[role='link'].deque-link.active,
a.deque-link.active {
  color: #000000;
}
.deque[role='link'].deque-link:visited,
a.deque-link:visited {
  color: #5C2E91;
}

Implementation Instructions

Implementing the Link pattern is done to ensure non-anchor links are accessible, whether a regular, span, or image link.

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

For each aria-link, create a container (e.g. <span>) with the following three attributes:

  • role="link" to designate the element as a link
  • data-href: the URL of the link destination
  • class="deque-link-aria" to initialize the links on the page and class="deque-link" for styling purposes.
  • Optional: data-target to designate the window target, e.g. data-target="_blank" to force the link to open in a new window.