A common cause of non-accessible web pages are elements that were made interactive via JavaScript but cannot be focused or activated with anything but the mouse.
❌ Bad example
Let's take a look at a common example:
<form filter>
<input filter--query name="query" type="text">
<span filter--reset>Clear Search</span>
</form>
The HTML above is being activated with an Unpoly compiler Show archive.org snapshot like this:
up.compiler('[filter]', function(filterForm) {
const resetButton = filterForm.querySelector('[filter--reset]')
const queryInput = filterForm.querySelector('[filter--query]')
function resetQuery() {
queryInput.value = ''
queryInput.focus()
}
up.on('click', resetQuery)
})
The Clear search button has three issues:
- It cannot be focused with the keyboard
- It cannot be pressed with the keyboard
- A screen reader will not announce it as a button
✔️ Good example
To make the reset button focusable with the keyboard, add the [tabindex]
attribute to the element that usually receives the click
event.
Also set an [role=button]
attribute so screen readers will announce it as a "button" when focused:
<form filter>
<input filter--query name="query" type="text">
<span filter--reset tabindex="0" role="button">Clear Search</span>
</form>
Now change your
Unpoly compiler
Show archive.org snapshot
to activate the button on keydown
in addition to click
:
up.compiler('[filter]', function(filterForm) {
const resetButton = filterForm.querySelector('[filter--reset]')
const queryInput = filterForm.querySelector('[filter--query]')
function resetQuery(event) {
evt.preventDefault()
queryInput.value = ''
queryInput.focus()
}
up.on('click', resetQuery)
up.on('keydown', (evt) => {
// ignore any other keys like tabbing or pressing shift etc.
if (evt.code === 'Space') {
resetQuery()
}
})
})
Links and buttons do not need additional code
Elements that are interactive by default, such as an <a>
or <button>
, do not need additional code:
- They can be focused without an additional
[tabindex]
attribute - They can be activated with the keyboard without custom JavaScript. They will emit a
click
event after keyboard activation.
By using a <button>
we can reduce the example to this:
<form filter>
<input filter--query name="query" type="text">
<button type="button" filter--reset>Clear Search</button>
</form>
Shorter solution with [up-clickable]
If you cannot use an element like <button>
, you may use the [up-clickable]
attribute to make elements interactive. Elements with this attribute gain the following behavior:
- The element is given an
[role=link]
Show archive.org snapshot
attribute so screen readers announce it as link. - The element shows
pointer
Show archive.org snapshot cursor when hovered over. - The element can be focused with the keyboard.
- The element emits an
up:click
event when activated. - You may also assign an
[up-instant]
Show archive.org snapshot attribute to make the element activate onmousedown
instead ofclick
.
This reduces the code to the following:
<form filter>
<input filter--query name="query" type="text">
<span filter--reset up-clickable>Clear Search</span>
</form>
up.compiler('[filter]', function(filterForm) {
const resetButton = filterForm.querySelector('[filter--reset]')
const queryInput = filterForm.querySelector('[filter--query]')
function resetQuery(event) {
event.preventDefault()
queryInput.value = ''
queryInput.focus()
}
up.on('up:click', resetQuery)
})