Accessible JavaScript Click Handlers

 |  Web Accessibility, Javascript

The Internet is a fantastic medium where people can share information, knowledge… and cute cat photos freely and inclusively. HTML is one of the key technologies for the World Wide Web and when used correctly, largely ensures content is accessible to all. Unfortunately, with the rush to create sophisticated JavaScript rich web pages, the fundamentals of good semantic markup are often overlooked to the detriment of web accessibility.

One way to drastically exclude users from your site is to add JavaScript click handlers to non-interactive elements such as <div> and <span> tags.


When is a button not a button?

Can you spot the difference between the two “buttons” below?

To a fully-sighted mouse or touch device user, these “buttons” would appear to function and look the same. For keyboard users or users of assistive technology though, it’s a very different story.

Let’s review these “buttons” as a keyboard user and a screen reader user.

The difference between the “buttons” is one is a native <button> while the other is a <div> tag. Unlike a native <button>, a keyboard user can’t interact with and invoke the click handler on the inaccessible <div> “button” and a screen reader user isn’t made aware that the inaccessible “button” is an element that could be interacted with.

Clearly, the simple fix for the inaccessible button is to swap the <div> for a <button> and job done… but this blog article would be a very short if we left it there! Instead, let’s explore the differences between these “buttons” further and see how we can replicate native <button> behaviour with a <div> element to highlight what using the correct semantic markup gives us out of the box.


Focusable element

The tab key allows a keyboard user to move focus through all the interactive elements on your page. Our <div> button is not focusable though, so let’s resolve that by adding the tabindex attribute.

<div class="btn" tabindex="0">Download</div>

By setting the tabindex to 0, we’re telling the browser that the element can receive focus and should do so in the same order it appears in the DOM, as a user tabs though all the interactive elements.

Web browsers provide a standard focus indicator in the form of an outline / focus ring around the focused element. The outline can be styled differently as required to match the design of your site but the outline should never just be removed.

button:focus {
  outline: 2px dashed red;
}

An element’s tabindex can be set to a positive integer which means the DOM order is ignored for the element. The browser will initially move focus through the elements with a tabindex greater than 0 (in the order of lowest tabindex to highest) before then moving focus through all the other interactive elements in DOM order. In general though, using tabindex to define an explicit tab order is not recommended.


Keyboard interaction

A keyboard user can now tab and set focus on the <div> button but they still can’t operate the button. With a native <button> element, when the button has focus, both Space and Enter keys will trigger the click event handler. To replicate this behaviour on our <div> button, we’ll need to add an event listener to onkeypress to determine if Space or Enter was pressed and if so manually trigger the click handler.

const button = document.getElementById('divButton');

button.addEventListener('click', () => {
  alert('Time to start downloading');
});

button.addEventListener('keydown', (event) => {
  if (event.code === 'Space' || event.code === 'Enter') {
    button.click();
  }
});

Screen readers

We’ll now turn our focus to the differences between a native <button> and our <div> button for screen reader users. When the native button received focus, it was announced with “Download, Button”. The screen reader announcing the word “Button” gives a visually impaired user more context to know the type of control that has focus and therefore to know it can be interacted with. The reason the screen reader announced the word “Button” is because a native button intrinsically has the attribute role of button. If we apply this attribute to our <div> button then the screen reader will announce in the same manner.

<div class="btn" tabindex="0" role="button">Download</div>

Note that adding the role of button just affects how an element appears to a screen reader. We still need to make the <div> focusable and react to keyboard events as detailed above.

Now that our <div> button is focusable, reacts to keyboard events and is announced correctly by screen readers, we have successfully replicated a native <button>.


Accessibility checking

There are a number of tools available to help check your site for common accessibility issues. Unfortunately, none of the the tools I tried (including the axe Chrome extension and Google’s Lighthouse identified the <div> “button” as an accessibility issue. I suspect this is due to them not evaluating for JavaScript related web accessibility issues.

Automated accessibility tools can be very useful but, as evidenced here, should be considered a part of your wider strategy around checking for accessibility issues. Arguably, one of the best checks you can perform is to unplug your mouse, turn off your screen and with the keyboard and a screen reader, ensure you are able to perceive and interact with your pages.


Summary

In almost all cases, click handlers should be applied to natively interactive elements like <button> and <a>. Only in exceptional cases should you consider adding click handlers to non-interactive elements and if you do go down this road, ensure you cover accessibility.