Accessibility: Making non-standard elements interactive

Updated . Posted . Visible to the public. Repeats.

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

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:

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)
})
Profile picture of Michael Leimstädtner
Michael Leimstädtner
Last edit
Henning Koch
License
Source code in this card is licensed under the MIT License.
Posted by Michael Leimstädtner to makandra dev (2024-03-05 13:57)