a11y

by Ron Valstar

HTML

<form action="#">
  <fieldset>
    <legend>login</legend>
    <label class="label"><span>username</span><input class="input" type="text" autofocus /></label>
    <label class="label"><span>password</span><input class="input" type="password" /></label>
    <div class="wrap">
      <button class="button">submit</button>
      <a href="#">forgot password</a>
    </div>
  </fieldset>
</form>
<div class="indicator indicator--mouse">mousing</div>
<div class="indicator indicator--touch">touching</div>
<div class="indicator indicator--keyboard">keyboarding</div>

SCSS

:root {
  --shade-inner: 1px 1px 0.375rem silver inset, 0 0 1px silver inset;
  --focus-halo: 0 0 0.25rem dodgerblue;
}
* {
  outline: none;
}
html {
  color: #333;
  background-color: whitesmoke;
}
body {
  padding: 0.5rem;
  font-family: sans-serif;
}
fieldset {
  border: none;
  box-shadow: 2px 2px 8px silver;
  padding: 1.5rem;
  margin: 0 auto;
  max-width: 32rem;
  background-color: white;
}
legend {
  font-weight: bold;
  color: #666;
}
.label {
  display: flex;
  margin-bottom: 1rem;
  line-height: 160%;
}
.label>* {
  flex: 1 1 50%;
}
.input {
  border: 0;
  box-shadow: var(--shade-inner);
  font-size: inherit;
  padding: 0 0.5rem;
}
.wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}
.wrap a {
  font-size: 0.75rem;
}
.button {
  line-height: 150%;
  transition: background-color 300ms linear;
  border: 1px solid #666;
  border-radius: 0.25rem;
  text-align: right;
  margin-bottom: 1rem;
  font-size: inherit;
}
.button:active {
  border-color: dodgerblue;
}
.button:focus {
  border-color: green;
}
.user-input--touch .button {
  font-size: 1rem;
}
.user-input--mouse .button:hover {
  background-color: powderblue;
}
.user-input--keyboard .input:focus {
  box-shadow: var(--shade-inner), var(--focus-halo);
}
.user-input--keyboard .button:focus {
  box-shadow: var(--focus-halo);
}
.user-input--keyboard a:focus {
  text-shadow: var(--focus-halo);
}
.indicator {
  max-width: 8rem;
  margin: 2rem auto;
  text-align: center;
  line-height: 160%;
  border-radius: 2rem;
  transition: background-color 300ms linear;
  box-shadow: 0 0 1rem darkgray;
  color: gray;
}
.indicator.indicator--event {
  transition: none;
  background-color: cyan;
}
.user-input--touch .indicator--touch,
.user-input--mouse .indicator--mouse,
.user-input--keyboard .indicator--keyboard {
  box-shadow:
    0 0 1rem lightsteelblue,
    0 -0.5rem 1rem lightskyblue inset,
    0 0.5rem 1rem lightcyan inset
    ;
  color: darkcyan;
}

JavaScript

/**
 * Module to check for user input mouse, touch and keyboard
 * Sets the input-state as a className onto the documentElement.
 * Stores the input-state in localStorage.
 * Has an API in form of getters and event dispatchers.
 */

const {documentElement} = document||globalThis.document
const {classList} = documentElement

const addEventListener = documentElement.addEventListener.bind(documentElement)
const removeEventListener = documentElement.removeEventListener.bind(documentElement)

const block = 'user-input'
const className = {
  mouse: `${block}--mouse`,
  touch: `${block}--touch`,
  keyboard: `${block}--keyboard`
}
const event = {
  mousemove: 'mousemove',
  touchstart: 'touchstart',
  keyup: 'keyup',
  click: 'click'
}

const call = fn => fn()

const navigationKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Escape', 'Enter', 'Tab']

const storageName = 'userInput'
const sessionStorage = window.sessionStorage
const getItem = sessionStorage.getItem.bind(sessionStorage)
const setItem = sessionStorage.setItem.bind(sessionStorage, storageName)

const userInputDefault = {mouse: null, touch: null}
const state = JSON.parse(getItem(storageName)||'null')||userInputDefault

const mouseListeners = []
const touchListeners = []
const keyboardListeners = []

const mouseMoves = new Array(10).fill(0).map((v, i) => i * 1E9)

setDocumentElementClasses()

!state.mouse && addEventListener(event.mousemove, onMouseMove, true)
!state.touch && addEventListener(event.touchstart, onTouchStart, true)

addEventListener(event.keyup, onKeyUp, true)
addEventListener(event.click, onClick, true)

/**
     * Returns whether the device has mouse input
     * @returns {boolean}
     */
/*export*/ function isUsingMouse() {
  return state.mouse
}

/**
     * Returns whether the device has touch input
     * @returns {boolean}
     */
/*export*/ function isUsingTouch() {
  return state.touch
}

/**
     * Check if keyboard is used over mouse
     * @returns {boolean}
    ...