React and Accessibility
Why should we care about accessibility (A11Y)?
I used to think of accessibility as developing websites for blind people. I couldn't be more wrong.
Accessibility impacts more people than we think. There are around 1.3 billion people with disabilities in the world. The equivalent to the whole chinese market.
There are several types of barrier that impact users on the web(opens in a new tab):
- auditory disabilities
- cognitive disabilities
- physical disabilities
- speech disabilities
- visual disabilities
- Aging(opens in a new tab)
Additionally, some people might be experiencing temporary impairments that may occur due to an accident, surgery, or medication.
Another good reason to invest in accessibility is compliance or certification.
Most countries already require some sort of accessibility compliance with WCGA 2.0(opens in a new tab), or Section 508(opens in a new tab), or ADA(opens in a new tab). In some countries, regulation also forces private companies to adhere to accessibility standards, such as Norway.
Now is a good time for you to start investing in accessibility.
Building more accessible web applications and websites is easier than you think.
Web technologies are getting more accessible, and there are a ton of accessibility tools(opens in a new tab) that developers can use. It has never been easier to start.
However, most websites have accessibility errors.
(2 slides were omitted from original talk)
The WebAIM Million(opens in a new tab) evaluated the top 1 million homepages using automated testing tools.
Automated accessibility testing is not perfect. In fact, it only catches around 30% of all accessibility errors (you should always perform manual tests(opens in a new tab) your website).
But even so, the study found that almost 98% of websites had accessibility issues.
Websites using React had, on average, 10% more accessibility problems.
We are preventing people from using our websites by making them inaccessible.
We need to do better as web developers. We need to create more inclusive websites because this part is of our job.
We will see some accessibility tips and best practices in React. However, most of this accessibility guidelines applies to other frontend frameworks. This is not meant to be a React vs Vue vs Angular type of argument.
It contains structural patterns, interaction patterns and application patterns.
Accessible Structural Patterns
HTML5 Landmarks
People without any visual disability are used to open a website and see all the content at a glance. We can see the header, navigation links, and we can already see where and what the main content of the page is. We immediately know what the page is all about.
People who navigate the internet using screen reader software have a completely different experience.
Learn how to test your website with a screen reader(opens in a new tab).
(7 slides were omitted from original talk)
There are several types of landmarks, and they usually have HTML5 equivalent tags.
For example, if you use the header
HTML5 tag, and it is not within any article
, aside
, main
, or section
, it will be inferred as having the header
role by screen readers.
The main
tag, which you should only have one per page, will also be automatically inferred as the main
landmark.
The footer
tag will also be inferred as the contentinfo
landmark if not present within any article
, aside
, main
, or section
tag.
The nav
tag will be automatically inferred as the navigation
landmark.
The section
tag will be automatically inferred as the region
landmark.
To provide more information to screen reader users you should label your section landmarks.
The form
tag will be automatically inferred as the form
landmark.
The aside
tag will be automatically inferred as the complementary
landmark.
There is one more landmark type, the search
landmark, that has no HTML5 equivalent.
Let's see how can we use landmarks to create more accessible websites.
In this example, the header
, main
, and footer
elements are automatically assigned their landmark roles.
We can then override when needed. In this case, there is a search form on the page. We can enhance this form by passing the attribute role="search"
, making this a search
landmark.
Because we have multiple nav
elements on the page, we can label each one using the attribute aria-label="primary"
and aria-label="secondary"
correspondently.
It&s redundant to write aria-label="primary navigation"
as screen-readers already know that the landmark is of type navigation
. Otherwise, the screen-reader announces it as “primary navigation navigation”, which is quite annoying.
We should also make use of the aria-label
attribute in the search input field. Visually, our design is enough to convey the meaning of the form, but screen reader users will have no information for this field.
Similarly, we can make use of the attribute aria-labelledby
and reference an ID of another element in the page. This element acts as a label of the section
.
Forms Accessibility
Everyone hates forms. The experience for screen reader users is even worst.
Here are 3 tips to make forms accessible.
(1 slide was omitted from original talk)
Accessibility Tip #1
Always label your input
elements.
The easiest way to do this, is to surround your input
element with a label
, and add the labelling text as a sibling of the input
.
Another way to label input
elements, is to make use of the htmlFor
attribute (in JSX, the html attribute for
is used as htmlFor
).
The value of this attribute needs to be a valid ID pointing to an input
element with that ID.
But careful with this approach in React. If you are writing a component, you don't know where and when it is going to be used. You can't assume that it will only be used once on the page.
If you are using the component, be sure to make this IDs unique. I recommend you use the first approach, as to avoid this issue altogether.
Labelling an input is extremely important. Even if your designer didn't visually put a label there.
I'm talking about input fields that visually look like their label is the placeholder.
Screen readers don't consider the placeholder
attribute as a label, and it won't be announced. Nonetheless, many of those input
elements on the are implemented like this.
To make this input
accessible in React you can make use of a nice <VisuallyHidden />
component.
With only a few lines of CSS, we can hide the element in the screen but still make it announced by screen readers.
You can use it as you would use a label
element, except, nothing will be displayed on the website.
Accessibility Tip #2
Another way to label your inputs (or anything else) is to use aria-label
or aria-labelledby
attributes.
Icons accessibility
Another common element on websites, are icon links or icon buttons icon. In this example, with an arrow icon.
The icon in this scenario is only used as a decorative element, and it doesn't constitute any type of textual meaning we want our screen-reader users to be read.
We can make use of an aria-hidden="true"
attribute, to hide the element from the accessibility tree. This makes screen-readers not announce the element for our users.
A similar example is when we have links that are only made out of icons. For example, we often see social links in the footer of websites, where the logo of these social networks is used for the link itself.
And while this is effective for people without visual disabilities, people who can't see the icon have no idea where the links points to.
We can make accessible icon buttons in two steps:
- hide the link from the accessibility tree, by using
aria-hidden="true"
on the element with the image, usually ansvg
. - provide a text alternative to the link, by using the
aria-label
attribute on the link it-self.
(1 slide was omitted from original talk)
Accessible Interaction Patterns
Form Validation
These examples make use of Formik(opens in a new tab), a React package that makes form validation a lot easier.
One error that we sometimes do is how to associate form errors with their inputs.
The right way to do it is to use the attribute aria-describedby
, with the value being the id of the error element associated with the input.
One note, make sure the IDs are unique within a page.
There are two ways we can improve form validations.
The first is to provide live form validation, as the user types. For screen-reader users, this means adding the attribute aria-live="polite"
to the error element.
When text changes inside this element, the screen reader will announce it.
However, for some types of fields, we don't want the errors to be shown or announced as the user types.
Take the example of an email field. When the user starts typing, the field is invalid. It will only become a valid email, when the user is very close to finishing typing the email address. We don't want the error to appear visually as the user types, or being announced by our screen readers.
To change this behavior, we can change the aria-live
attribute to assertive
. This causes screen readers to only announce the error when the user leaves the field by “un-focusing” it.
Icon Toggle Buttons
They are used as “on/off” switches. Spotify uses this in multiple places throughout their web application, including to “mute/un-mute” the sound.
(1 slide was omitted from original talk)
Let's consider a button responsible for “muting/un-muting” the sound. Visually, the button is only shown with two icons, one when it's active (sound is on) and one when it's inactive (sound is off).
The first thing we need to do, is to provide a text alternative for the button. We can do that by adding the aria-label="Mute"
attribute.
The second thing we need to do, is to mark the icon as purely decorative, by adding an aria-hidden="true"
attribute.
And finally, we need to make this button behave like a toggle and not a button. The difference between them is that the toggle has an internal state (signalled visually as an icon change) that makes the button active/inactive.
We can provide this state to screen reader users by adding an aria-pressed="true"
or aria-pressed="false"
.
As we're creating an icon button in React, we can abstract this in a prop of type boolean
(yay, React!).
Accessible Application Patterns
Accessible React Router
Most React applications have routing, such as the popular library React Router(opens in a new tab), which has a few accessibility issues.
When a user navigates to a new page, nothing is announced by screen reader users. It's like nothing even happened.
We can fix the react router problem with focus management.
When we change to a new page, we need to change the focus to the new component that was just mounted.
There are a few things that we need to do to achieve this in React.
First, we need to add a tabIndex={-1}
, in order to make the container of the new page “focusable” (it will likely be a “non-focusable” element such as a section
).
We should also make sure that this element is properly labelled. As the new page will likely have an h1
element, we can use this element to label the container by adding id="title"
attribute to the heading and a aria-labelledby="title"
.
Lastly, we need to move the focus to this container element (now a “focusable” element).
To achieve this in React, we first need to create a variable containing a ref
to the element.
And finally, once we have a ref
to the element, we can focus the element once the component mounts.
This example uses React Hooks(opens in a new tab) to focus an element by calling ref.current.focus()
inside a useEffect
callback.
If you're not using hooks, you can achieve the same by calling this inside a componentDidMount
.
Accessible Notifications
Web applications need to notify the user something has happened. Consider an e-commerce website, we need to notify the user once an item has been added to the cart.
We would have a cart icon with a number of items in it displayed. No problem with this for people with no visual disabilities, as they can not only see the total number of items in the cart at a glance, but also see when a new item is added.
To make this pattern accessible for people using a screen reader, we can start by making use of the <VisuallyHidden />
component to provide a text alternative to this information.
We can then add two html attributes: role=status
– to signal that this component will have live messages being announced – and aria-live="polite"
– so that when new content is added inside of it, it's immediately announced without user interaction.
Another similar pattern, is when we have global flash messages being shown the user. This messages would be shown as soon as something relevant happens like updating data on a user profile, internet connection being lost, etc.
Let's assume we have a component called <Announcements />
that receives a prop announcements: string[]
. This component is responsible for showing incoming messages. We can imagine this component being used once as part of the application layout.
In this example, each message is preceded by an icon showing if it's a success, warning, or error message (note that is good to have an icon and not only a color, as we should never rely on color alone as some people might suffer from color blindness).
To convey the same meaning of the icon for people using a screen reader, we need to first add an aria-hidden="true"
to the image html element.
And also provide a text alternative for the icon meaning, using the <VisuallyHidden />
component.
Lastly, as new announcements will be shown over time, we need to add an aria-atomic="true"
attribute, which makes screen readers only announce newly added content.