Make React Navigation Accessible Again

John Luke Garofalo - Aug 11 '17 - - Dev Community

Imgur

Early last week, our product manager finally convinced us to spend a day figuring out how to make our React App more accessible. If you're like me, you took a course in school that spent a semester explaining the importance of accessibility on the web and introducing you to the people who are mostly affected. If you haven't familiarized with this issue, then I highly suggest you do because you will learn how much we take for granted while using the internet every day.

We struggled at first with the right solution as React Accessibility documentation is pretty minimal when it comes to more complex situations, in my opinion. They do a great job of explaining the "why" it's important but not really the "how". We had a pretty large app that needed to be fully navigate-able with the tab, shift + tab, & enter key. We also needed to make sure that every button would provide feedback when :focused. I'm going to show you how we approached this problem as pragmatically as possible. Obviously, it's not perfect and we can always improve our accessibility, but I want to share what I learned to show you that you can make small changes that convert to massive improvements for anyone with disabilities using your React app.


Adding jsx-a11y to eslint

This eslint-plugin-jsx-a11y plugin helps to suggest accessibility changes that you can make to your app as you develop. I think this is really important in regards to learning about the accessibility best practices in an inline manner. We use eslint here at Fixt Inc. so I'm partial to it, but I'm sure there are equivalents out there.


!!Tab

Imgur

A huge gain, with minimal effort, is simply making it possible to tab through all of the buttons in your app without using the mouse at all. This is really useful for webforms but also outside of webforms.

Let's start by creating a custom Button component in React and making it tab-able.

import React from 'react';
import PropTypes from 'prop-types';

const Button = ({ children, onClick }) => (
  <div onClick={ onClick } tabIndex={ 0 }>
    { children }
  </div>
);

Sweet. As you can see, the tabIndex prop is the real hero here. This global attribute can have three different types of values, according to the MDN docs:

  • Negative Value
    • A negative value (usually tabindex="-1" means that the element should be focusable, but should not be reachable via sequential keyboard navigation. Mostly useful to create accessible widgets with JavaScript.
  • Zero Value
    • tabindex="0" means that the element should be focusable in sequential keyboard navigation, but its order is defined by the document's source order.
  • Positive Value
    • A positive value means the element should be focusable in sequential keyboard navigation, with its order defined by the value of the number. That is, tabindex="4" would be focused before tabindex="5", but after tabindex="3". If multiple elements share the same positive tabindex value, their order relative to each other follows their position in the document source.

Simple enough, right?


(Enter || Return) key

So, now that we can tab to our button, we want the user to be able to click the Enter or Return key to simulate a click because what good is tabbing to buttons that you can't click on?

Let's build a function that will help us to do just that:

const buildHandleEnterKeyPress = (onClick) => ({ key }) => {
  if (key === 'Enter') { 
    onClick(); 
  }
};

There are a few things going on here that may be confusing if you've never encountered them before.

This function is a curried function meaning it's a function that returns a function. I'm not going to go into explaining this in-depth, but if you're unfamiliar with this concept, I'm going to explain why we need this. In our situation, we want to provide a function to our component that will handle whenever a key is pressed. Since we can assume that we will know what the value of onClick is at the time that buildHandleEnterKeyPress is invoked, then we can create a function that uses the onClick function. This allows us to pass any callback function to buildHandleEnterKeyPress and it will execute when a key is pressed while the user is focused on a given element.

So now we can head to our Button component and use this function to get our desired result:

const Button = ({ children, onClick }) => (
  <div 
    onClick={ onClick } 
    onKeyPress={ buildHandleEnterKeyPress(onClick) } 
    tabIndex={ 0 }
  >
    { children }
  </div>
);

This is really simple for the developer and really important to the people on the internet who absolutely need to be able to use the keyboard to navigate.


Conclusion

As I mentioned, this is about the bare minimum you can do in regards to accessibility. There are so many good resources and best practices out there that we should put aside our laziness to do the right thing.

Terabox Video Player