Updated: Accessibility: Making non-standard elements interactive

Rewrote most of the card.

Changes

  • 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
  • +## ❌ Bad example
  • Let's take a look at a common example:
  • ```html
  • -<div filter>
  • - <input filter--query="true" id="query" name="query" type="text">
  • - <label filter--reset="true">Clear Search</label>
  • -</div>
  • +<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](https://unpoly.com/up.compiler) like this:
  • ```js
  • 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)
  • })
  • ```
  • -You can only use the "clear query field" shortcut by clicking on the "Clear Search" label with a mouse.
  • +The *Clear search* button has three issues:
  • -## Good
  • +- It cannot be focused with the keyboard
  • +- It cannot be pressed with the keyboard
  • +- A screen reader will not announce it as a button
  • -It's super easy to make this feature accessible: add the `tabindex` attribute to the element that usually receives the `click` event - it is now focusable with keyboards as well. Then, add a second listener for the `keydown` event, it should do exactly the same thing when pressing `<Space>`:
  • +
  • +## ✔️ 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:
  • ```html
  • -<div filter>
  • - <input filter--query="true" id="query" name="query" type="text">
  • - <label filter--reset="true" tabindex="0">Clear Search</label> <!-- now with a tabindex -->
  • -</div>
  • +<form filter>
  • + <input filter--query name="query" type="text">
  • + <span filter--reset tabindex="0" role="button">Clear Search</span>
  • +</form>
  • ```
  • -The HTML above is being activated with an [Unpoly compiler](https://unpoly.com/up.compiler) like this:
  • +Now change your [Unpoly compiler](https://unpoly.com/up.compiler) to activate the button on `keydown` in addition to `click`:
  • ```js
  • up.compiler('[filter]', function(filterForm) {
  • const resetButton = filterForm.querySelector('[filter--reset]')
  • const queryInput = filterForm.querySelector('[filter--query]')
  • - function resetQuery() {
  • + 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') {
  • - evt.preventDefault() // prevent scrolling down on the page
  • resetQuery()
  • }
  • })
  • })
  • ```
  • -## Note
  • -If you are listening on the `click` event on links, buttons or other default interactive elements you don't have to change anything. In fact, the best way to ensure accessibility is to use only elements with a corresponding semantic meaning.
  • +## 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:
  • +
  • +```html
  • +<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]`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/link_role)
  • + attribute so screen readers announce it as link.
  • +- The element shows [`pointer`](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) 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]`](https://unpoly.com/a-up-instant) attribute to make the element activate on `mousedown` instead of `click`.
  • +
  • +This reduces the code to the following:
  • +
  • +```html
  • +<form filter>
  • + <input filter--query name="query" type="text">
  • + <span filter--reset up-clickable>Clear Search</span>
  • +</form>
  • +```
  • +
  • +```js
  • +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)
  • +})
  • +```
  • +
  • +
Henning Koch About 1 month ago