Skip to content

Code to snag for commonly used tags / js widgets with appropriate aria labels/tags/functionality

Notifications You must be signed in to change notification settings

JoshMK/html-js-accessibility-snippets

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 

Repository files navigation

HTML-JS-accessibility-snippets

Code to snag for commonly used html chunks and js widgets with appropriate aria labels, tags, and functionality.

Accordions

Examples:

HTML - accordion markup with necessary aria attributes

<html lang="en" charset="UTF-8">
	<title>Accessible Accordion</title>
	<head>
		<link rel="stylesheet" href="accessible-accordion.css" />
	</head>
	<body>
		<!-- widget code begins here -->
		<div class="accessible-accordion">
			<!-- replace h1 with appropriate nested heading level - see https://www.w3.org/WAI/tutorials/page-structure/headings/ -->
			<h1 class="accessible-accordion__header">
				<button
					type="button"
					class="accessible-accordion__trigger"
					aria-expanded="false"
					onclick="toggleTrigger(this)"
				>
					[ACCORDION BUTTON TEXT GOES HERE]
				</button>
			</h1>
			<div class="accessible-accordion__panel" aria-hidden="true">
				<p>[CONTENT GOES HERE]</p>
				<img src="" alt />
			</div>
		</div>
		<script src="accessible-accordion.js"></script>
	</body>
</html>

CSS - basic accordion styles

:root {
	--aa-header-brd-col: #500000;
	--aa-trigger-bg-col: #e8e8e8;
	--aa-trigger-bg-col-hover: #f5f5f5;
	--aa-trigger-bg-col-toggled: #929292;
	--aa-trigger-txt-col: #500000;
	--aa-trigger-toggled-txt-col: #f5f5f5;
	--aa-arrow-col: #000;
	--aa-arrow-toggled-col: #f5f5f5;
}

.accessible-accordion__header {
	border: 1px solid var(--aa-header-brd-col);
	margin-bottom: auto;
}

.accessible-accordion__panel {
	padding: 0 0.5em;
}

.accessible-accordion__trigger {
	position: relative;
	cursor: pointer;
	border-width: 0;
	padding: 1em 2.75em 1em 1em;
	width: 100%;
	text-align: left;
	background-color: var(--aa-trigger-bg-col);
	color: var(--aa-trigger-txt-col);
}

.accessible-accordion__trigger:hover,
.accessible-accordion__trigger:focus {
	background-color: var(--aa-trigger-bg-col-hover);
}

.accessible-accordion__trigger:after {
	content: "";
	display: block;
	width: 0;
	height: 0;
	position: absolute;
	background-color: transparent;
	top: 1.25em;
	right: 1em;
	border-left: 0.5em solid transparent;
	border-right: 0.5em solid transparent;
	border-top: 0.625em solid var(--aa-arrow-col);
	transform-origin: center center;
	transform: rotate(0deg);
}

.accessible-accordion__trigger[aria-expanded="true"] {
	color: var(--aa-trigger-toggled-txt-col);
	background-color: var(--aa-trigger-bg-col-toggled);
}

.accessible-accordion__trigger[aria-expanded="true"]:after {
	transform: rotate(180deg);
	border-top-color: var(--aa-arrow-toggled-col);
}

.accessible-accordion__panel {
	max-height: 0vh;
	overflow: hidden;
	opacity: 0;
	visibility: hidden;
	padding: 0;
	transition: max-height 0.2s ease-in-out, padding-top 0.2s ease-in-out,
		padding-bottom 0.2s ease-in-out, opacity 0.2s ease-in;
}

.accessible-accordion__panel[aria-hidden="false"] {
	max-height: 100vh;
	overflow: auto;
	visibility: visible;
	opacity: 1;
	padding: 1em 0;
}

JS - basic accordion javascript

//ES5 version

//get all accordions in an array
var accordions = [].slice.call(
	document.querySelectorAll(".accessible-accordion")
);

//set ids for each accordion on page load
accordions.forEach(function(accordion, i) {
	accordion.children[0].children[0].setAttribute(
		"aria-controls",
		"accessible-accordion-panel-" + i
	);
	accordion.children[1].setAttribute("id", "accessible-accordion-panel-" + i);
});

//show/hide individual accordion panels
function toggleTrigger(trigger) {
	var panelExpanded = trigger.getAttribute("aria-expanded");
	console.log(panelExpanded);
	accordions.forEach(function(accordion, i) {
		var panelHidden = panelExpanded === "false" ? "false" : "true";
		accordion.children[1].setAttribute("aria-hidden", panelHidden);
	});
	if (panelExpanded === "false") {
		trigger.setAttribute("aria-expanded", "true");
	} else {
		trigger.setAttribute("aria-expanded", "false");
	}
}

References

Forms

Notes

  • Use a <label for={name}> tag for each <input id="{name}"> field
  • Wrap grouped controls with <legend> and <fieldset> when a higher level description (i.e., the legend) is necessary. Contextually obvious tags (i.e., <select>) don't need to be wrapped.
  • <input type='submit'> and <input type='reset'> - always supply a value= attribute (screen readers read these aloud for context).
  • <button> - always supply text between the opening/closing </button> tag or use a WCAG compliant substitute. These are:
    • <button id="text">Name</button>
    • <button id="al" aria-label="Name"></button>
    • <button id="alb" aria-labelledby="labeldiv"></button>
    • <button id="combo" aria-label="Aria Name">Name</button>
    • <button id="buttonTitle" title="Title"></button>
  • use <label> for better accessibility/support instead of the placeholder attribute if possible.
  • for the placeholder attribute, make sure its text contrast is adjusted for WCAG using custom CSS (the default values are noncompliant - see correcting code snippet below)

Examples:

HTML - Standalone Label Examples

<label for="firstname">First name:</label>
<input type="text" name="firstname" id="firstname"><br>

<input type="checkbox" name="subscribe" id="subscribe">
<label for="subscribe">Subscribe to newsletter</label>

HTML - Standalone Button and Input Button Examples

<button type="submit">Submit</button>
<button type="button">Cancel</button>

<input type="submit" value="Submit">
<input type="button" value="Cancel">

HTML - Related Inputs Grouped with Fieldset

<fieldset>
<legend>Output format</legend>
  <div>
    <input type="radio" name="format" id="txt" value="txt" checked>
    <label for="txt">Text file</label>
  </div>
  <div>
    <input type="radio" name="format" id="csv" value="csv">
    <label for="csv">CSV file</label>
  </div>
</fieldset>

HTML - Related Inputs Grouped with role="group", id, and aria-labelledby={id}

<div role="group" aria-labelledby="shipping_head">
	<div id="shipping_head">Shipping Address:</div>
	<div>
		<label for="shipping_name">
      <span class="visuallyhidden">Shipping </span>Name:
    </label><br>
		<input type="text" name="shipping_name" id="shipping_name">
	</div>
</div>
<div role="group" aria-labelledby="billing_head">
	<div id="billing_head">Billing Address:</div>
	<div>
		<label for="billing_name">
      <span class="visuallyhidden">Billing </span>Name:
    </label><br>
		<input type="text" name="billing_name" id="billing_name">
	</div>
</div>

HTML - Select Grouped with optgroup and label attribute

<select>
	<optgroup label="8.01 Physics I: Classical Mechanics">
		<option value="8.01.1">Lecture 01: Powers of Ten</option>
		<option value="8.01.2">Lecture 02: 1D Kinematics</option>
		<option value="8.01.3">Lecture 03: Vectors</option>
	</optgroup>
	<optgroup label="8.02 Physics II: Electricity and Magnestism">
		<option value="8.02.1">Lecture 01: What holds our world together?</option>
		[…]
	</optgroup>
	[…]
</select>

CSS - Example Placeholder Color Contrast Adjusted for a White Background

::-webkit-input-placeholder {
	 color: #767676;
	 opacity: 1;
}

:-moz-placeholder { /* Firefox 18- */
	 color: #767676;
	 opacity: 1;
}

::-moz-placeholder {  /* Firefox 19+ */
	 color: #767676;
	 opacity: 1;
}

:-ms-input-placeholder {
	 color: #767676;
	 opacity: 1;
}

References

Hamburger Menu

Notes

  • Use a semantic element for the burger (i.e. <button> - it has inbuilt keyboard functionality which is dope.)
  • Make sure the text "menu" is included within the semantic element to give it meaning to SRs (it can be concealed visually for non-SR users with CSS.)
  • If the hamburger menu uses any decorative symbols ('x', '-', etc.), make sure to set aria-hidden="true" for them (SR's will try to read them otherwise.)
  • No consensus on whether or not to use "role=navigation" within <nav> elements. MDN says using <nav> will automatically communicate its role (preventing redundant readings), yet other sources such as the W3 wiki say to explicitly communicate the role for tech that relies on aria labels and doesn't support HTML5. A lot of articles advocating <nav role="navigation"> are 5-6 years old, so it's unclear what SR/accessibility technology support for HTML5 is these days.

Example:

HTML

<header>
  <nav> <!-- afaik role="navigation" is redundant here, use that for divs if you gotta --> <!-- provide specific role if multiple navs, for example here: role="Main" -->
    <button id="toggleMenu" aria-expanded="false" aria-controls="menu">Menu</button>
    <ul id="menu">
      <!-- menu items -->
    </ul>
  </nav>
</header>

JS

(function () {
const toggleMenu = document.querySelector('#toggleMenu');
const menu = document.querySelector('#menu');
//
toggleMenu.addEventListener('click', () => {
  if (menu.classList.contains('is-active')) {this.setAttribute('aria-expanded', 'false'); menu.classList.remove('is-active');}
  else {menu.classList.add('is-active'); this.setAttribute('aria-expanded', 'true');}
});
})();

References

Inline SVGs as Non-Decorative Images

Example:

<svg role="img" aria-labelledby="svgTitle-{variableName}-0 svgDesc-{variableName}-0"> <!-- index-based ids for looped/mapped exports -->
<g><title id="svgTitle-{variableName}-0">Halo's a Pretty Cool Guy</title> <!-- make sure 'title' is the first child of the parent element -->
<desc id="svgDesc-{variableName}-0">He kills aliens and doesn't afraid of anything.</desc></g>
</svg>

References

Accessible Navigation

Example 1 - Using Javascript (ES5 Version)

HTML

<div class="page">
  <header role="banner" aria-label="Primary">
    <nav role="navigation" aria-label="Primary" id="nav" class="nav">
      <ul id="nav_inner" class="nav__list">
        <li class="has-drop">
          <a href="#!">
            Drop Down
          </a>
          <ul class="nav__list__drop">
            <li>
              <a href="#!">
                Item 1
              </a>
            </li>
            <li>
              <a href="#!">
                Item 2
              </a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#!">
            Normal
          </a>
        </li>
        <li class="has-drop">
          <a href="#!">
            Drop Down 2
          </a>
          <ul class="nav__list__drop">
            <li>
              <a href="#!">
                Item 1 has a long name
              </a>
            </li>
            <li>
              <a href="#!">
                Item 2
              </a>
            </li>
          </ul>
        </li>
      </ul>
    </nav>
  </header>
</div>

CSS

*,
*:before,
*:after {
  box-sizing: border-box;
}

html,
body {
  font-family: helvetica, arial, sans-serif;
  font-size: 18px;
  margin: 0;
  padding: 0;
}

nav ul,
nav ol {
  list-style: none;
  padding: 0;
  margin: 0;
}

.sr-only {
  position: absolute !important;
  clip: rect(1px, 1px, 1px, 1px);
  font-size: 1px;
}

.show-nav {
  position: fixed;
  top: 0;
  right: 0;
}

.nav__list {
  background: #fafafa;
  border-bottom: 1px solid #444;
  text-align: center;
  position: relative;
  z-index: 2;
}

.nav__list > li {
  display: inline-block;
}

.nav__list a {
  display: block;
  padding: 20px;
  position: relative;
  z-index: 2;
}

.nav__list a:hover,
.nav__list a:focus {
  background: #222;
  color: #fff;
}

.has-drop {
  position: relative;
}

.nav__list__drop {
  left: 0;
  margin: 0;
  position: absolute;
  text-align: left;
  top: 100%;
  opacity: 0;
  transform: translateY(-20px);
  height: 1px;
  transition: transform .2s ease-in-out,
              opacity .1s ease-out;
  overflow: hidden;
  z-index: 1;
}

.nav__list__drop {
  background: #fafafa;
  border: 1px solid #444;
  border-top: 0;
  min-width: 100%;
}

.nav__list__drop a {
  padding: 12px 20px;
  white-space: nowrap;
}

.nav__list a:focus + .nav__list__drop,
.has-drop:hover .nav__list__drop,
.nav__list__drop.has-focus {
  opacity: 1;
  transform: translateY(0px);
  height: auto;
  z-index: 1;
}

.no-js .nav__list__drop {
  display: none;
}

.no-js .has-drop:hover .nav__list__drop {
  display: block;
}

JavaScript

(function ( w, doc ) {
  // Enable strict mode
  "use strict";
  // Local object for method references
  var DropNav = {};
  // Namespace it up yo
  DropNav.ns = "Drop Navigation";
  // the main event...err..function
  DropNav.init = function () {
    var hasDrop = doc.querySelectorAll('.has-drop'),
      hasDropLinks = doc.querySelectorAll('.nav__list__drop a'),
      hasDropCount = hasDrop.length,
      hasDropLinksCount = hasDropLinks.length,
      i;
    if ( hasDropCount > 0 ) {
      for ( i = 0; i < hasDropCount; i++ ) { // i++  =  i = i + 1 
        var drop = hasDrop[i],
          firstDropLink = drop.querySelectorAll('.nav__list__drop a')[0];
        firstDropLink.innerHTML = ' <span class="sr-only">Sub menu, </span>' + firstDropLink.innerHTML; //*
      }
      for ( i = 0; i < hasDropLinksCount; i++ ) {
        var dropLinks = hasDropLinks[i];
        dropLinks.addEventListener('focus', function ( e ) {
          this.parentNode.parentNode.classList.add('has-focus');
        });
        dropLinks.addEventListener('blur', function ( e ) {
          this.parentNode.parentNode.classList.remove('has-focus');
        });
      }
    }
  };
  DropNav.init();
})( this, this.document );

References

Skip to Main Content Link

Notes

  • In cases where this link needs to be hidden for design reasons, use a library like WhatInput to hide it when not tab focused.
  • setting 'tabindex=-1' on the 'main' element (or whatever element it skips to) is supposed to correct a bug preventing it from being focused without a tabindex attribute in older IE versions.

Example:

HTML

<a className="app__skipNavLink" href="#main">Skip to main content</a>
<main id="main" tabindex="-1">{site content}</main>

CSS

.app__skipNavLink {
  z-index: -999;
  opacity: 0;
}

.app__skipNavLink:focus {
  opacity: 1;
  z-index: 999;
}

References

About

Code to snag for commonly used tags / js widgets with appropriate aria labels/tags/functionality

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published