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}
...