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 clickevent 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 
  pointerShow archive.org snapshot cursor when hovered over.
- The element can be focused with the keyboard.
- The element emits an up:clickevent when activated.
- You may also assign an 
  [up-instant]Show archive.org snapshot attribute to make the element activate onmousedowninstead 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)
})