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
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.
Top comments (6)
While this example does make the code more accessible for people using the keyboard it is missing a key attribute for assistive technologies such as screen readers. When a screen reader puts focus on a div it announces the text followed by group, which provides no indication the the element is clickable. If the attribute role="button" is added to the div the screen reader announces the the text followed by button which does provide context.
With that being said HTML has a button element which by default receives keyboard focus, will trigger the click event with the enter key and is announced as a button. If you changed your div to the button you wouldn't need to write additional code to make it accessible.
You are absolutely right! As I said, there are many more optimizations and this was by no means, an exhaustive guide on accessibility. It was merely just a couple tips that I picked up. The context tip is great and would be a great addition to this article.
This is also correct. The reason I wrote this article was because many React developers simply can't (or don't want) to use
button
in all cases. If you work in React land enough, you sometimes hit weird cases where proper semantic html elements and events cause strangeness in conjunction with the event loop of React.In my experience with Angular and React training, developers don't use proper markup because the sources they learn from don't use proper markup. This is not a slight at any group of developers, simply an observation from the sources I've learned from.
Accessibility has long been an ignored part of development. I feel like accessibility is finally starting to get its due like web standards did 13+ years back.
It is articles like yours that help push the idea of accessibility into other developers minds. While I'll still push for the use of semantic and accessible HTML by default over adding in fixes, this article has taken something totally inaccessible and made it more accessible. That is an enormous win for those that need it.
Keep on writing the good code. Lets go Os.
Is there a reason you decided to use a div and make it work like a button instead of just using a button element?
As I understand it, the first step in accessibility is using proper semantic HTML, since a great deal of this functionality is already built-in and handled by a11y tech, such as screen readers and Braille displays.
Yes there is, I'm glad you asked! Often at Fixt, we find ourselves in situations where a
button
simply wasn't the best tool for the job. This does bypass some of the incredible built in accessibility tools but many developers will make that trade-off, ignoring users with disabilities. This article was simply meant to bridge that gap with small changes that we can do, as developers.I hope I explained that well enough! :)
Thanks for the explanation. I've been pushing hard against non-semantic HTML in my company, since it seems so much like reinventing the wheel.
You're right though, that at least you're bringing the idea to the front of people's minds, and that's a win all by itself.