Getting started with web accessibility

Rachita Bansal
UX Collective
Published in
9 min readSep 9, 2020

--

A group of people using different devices, laptops, iPads and mobile phones.
Photo by Marvin Meyer on Unsplash

Web Accessibility

Website development can be broken down into smaller features that can be accomplished individually and are indispensable for a full-fledged app which can cater to all users. Accessibility is one such chunk and is actually a mammoth in its own. In this article, we’ll explore some tooling and concepts that can help you get started with basic accessibility of your app.

  1. ESLint plugin

Eslint is a great tool for controlling the code quality during web app development. For accessibility, react-a11y is an awesome eslint plugin which informs the developer of a11y issues during compile time.

For e.g <img src="/images/image1.jpg" /> ← eslint will warn the user of missing alt text that’s also read by assistive technology in place of visual image itself.

It can be installed using npm as below:

npm i eslint-plugin-jsx-a11y — save-dev

We need to then extend it to our eslint configuration file by adding the following code:

.eslintrc:

{ 
extends: [
“plugins:jsx-a11y/recommended”
],
plugins: [
“jsx-a11y”
]
}

And that’s it! Now, you’ll see some warnings in your code editor if you’re using VSCode and have installed the eslint extension or after running the lint command from command line.

2. React axe

React axe is another great tool that can be installed via npm. With very minimal configuration to be added at the top level of the app, it shows compile time a11y problems in dev-tools e.g color contrast issues etc. along with the severity levels.

Installation:

npm i — save-dev react-axe

Needs to be initialized at the top level of the app like so:

if(process.env.NODE_ENV === "development") {
const axe = import(() => "react-axe");
axe(React, ReactDOM, 1000)
}

Pros and cons:

  • Works best with Chrome.
  • De-dupes findings.
  • See errors with severity levels in dev-tools.
  • Can be configured by adding own config. that takes rules which is an array of objects. This is useful when you want to set custom rules accessibility for your app. For e.g.
const config = {
rules: [{
id: "radio group"
enabled: true
}]
}
if(process.env.NODE_ENV === "development") {
const axe = import(() => "react-axe");
axe(React, ReactDOM, 1000, config)
}

Read more about the configuration object API here: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure

3. Axe browser extension for Chrome / Addon for Firefox

React axe web extension will add a new tab in Chrome dev-tools called axe. Any page can be analyzed for accessibility issues in Chrome browser by clicking on the analyze button on left panel in theaxe tab. Try it out, it’s very interesting!

An image showing axe plugin tab on Chrome dev-tools.
axe plugin tab as seen on Chrome dev-tools

4. Tota11y from Khan academy — browser extension

This Chrome browser extension enables different plugins that can be run individually on a web page to provide a11y issues. This one also locates and shows where the issue is and prompts about the possible solutions to fix those issues. It’s also available as an add-on in Firefox.

5. High contrast — web extension

This Chrome extension enables color themes and provides color usage clarity in different modes. Download it here.

6. VoiceOver training in MacOS

Before getting started to test with SRs, VO training is a great material to be able to understand how the assistive technology should behave when reading different elements. Goto Settings → Accessibility → VoiceOver → Start the training!

An image showing MacOS accessibility settings dialog.
MacOS accessibility settings dialog

Some tips:

  • To enable VoiceOver in Mac press cmd + f5
  • To move through the web page and get into the web page use ctrl + option keys.
  • To bypass verbosity of the VO use ctrl key

7. Landmark regions

Landmark regions define the main areas of the app e.g main, navigation, headers and footers which are given specific roles in Aria.

An image showing WAI Aria specified landmark regions and their visual placement on a webpage.
WAI Aria specified landmark regions

We can define the app to contain these elements with these roles for e.g.

<div role="main">
<div role="navigation">
Back
</div>
<div role="contentinfo">
Footer
</div>
</div>

These landmarks can replaced with HTML5 sectioning elements for e.g. header, nav, footer and main which implicitly have the same aria roles as the elements above.

// Same as above code. 
// Converted to html5 elements.
<main>
<nav>
Back
</nav>
<footer>
Footer
</footer>
</main>

8. Adding Aria-label attributes

Sometime the labels of the elements are not very clear to the user when using VO like a button with only image. In that case we should explicitly add a property to the element called aria-label — a label that can be read by assistive technology as the label text for that element.

The aria-label property is useful when there is no visible text content that will serve as an appropriate accessible name.

Note: When aria-label is applied to elements that are given a role that supports naming from the string value in child elements for e.g. <button>close</button>, the descendent content is hidden from the SR and replaced by the aria-label attribute. For all other elements, both the descendent content as well as aria-label is read. For e.g. the SR will read only “change theme” for the button element shown below.

<button aria-label="Change Theme" class="simple-button">
light
</button>
Image showing a response from Tota11y Screen Reader plugin
Response from Tota11y Screen Reader plugin

9. Adding more accessible and contextual labels

aria-labelledby is a property used to group elements by taking in a list of ids separated by space.

This is useful where the sentence is formed of two elements but seems to be making more sense contextually when read together using assistive technology. For e.g.

<p>You don’t have any movies in your wishlist</p>
<link>Add some!</link> // is read separately as 2 sentences by SR

Can be re-written as:

// Read as a single sentence.
<div aria-labelledby="id-no-movies id-link">
<span id=“id-no-movies”>
You don’t have any movies in your wishlist
</span>
<link id=“id-link”>
Add some!
</link>
</div>

aria-labelledby has the highest precedence when browser calculates accessible names which means it overrides names from attributes like aria-label. It also incorporates hidden attributes or elements which have a CSS property of {display: none;}.

<span id="id-night-mode" hidden>Night mode</span>
<input aria-labelledby="id-night-mode" type="checkbox" role="switch" />

10. aria-describedby label text

aria-describedby can be used as similar to aria-labelledby. This property references the elements that describe an element as one single element.

For e.g. error text or validation text in a form field. When the VO reads the form input field, the error text and validation text is read with it as given below.

const helperId = helperText ? `{name}-helper` : "";
const errorId = errorText && !isValid ? `{name}-error`: "";
return (
<div>
<input
id="username-id"
name="username"
aria-describedby={`${helperId} ${errorId}`}
/>
{errorText &&
<div id={errorId}>
{errorText}
</div>
}
{helperText &&
<div id={helperId}>
{helperText}
</div>
}
</div>
);

11. Alternative text for images

Images need a meaningful text to read through the assistive technology. If an alternative text is not present, the VO just reads the filename of the image which can be less descriptive and provides less context.

In some cases, images don’t provide an additional context than the text and are really just decorative in nature. For those scenarios, we may want the images to not be read by assistive technology.

This can be achieved by passing the alternate text prop with a blank string. This would let the screen reader not announce the images while reading though the text.

// this image is not announced by the screen reader.
<img src=“/images/gladiator” alt="" />

12. Color contrast ratios

Chrome dev tools color picker tells the contrast ratio when inspecting an element.

An image showing color contrast ratio in chrome dev-tools color picker
Color contrast ratio in chrome dev-tools color picker

Usually both standards AA and AAA ratios of the WCAG standards need to be met, if AA is met but AAA doesn’t meet then the background needs to be updated and visa versa. At a minimum, color contrast ratio of the foreground to background must be 4.5:1.

Level access accessibility browser extension for Chrome provides useful suggestions on the background and foreground color combination which can help achieve both AA and AAA standards.

Another website for testing color contrast — https://color.review/

13. Live regions and aria-live attribute

There are four types of live-regions: aria-live, aria-relevant, aria-atomic, and aria-busy . Live regions dynamically get updated on the changes made to the element even if the focus is not on that element for e.g. error updates from AJAX request, live score updates of baseball match etc.

aria-live attribute notifies assistive technologies to announce the changes made on the element when the focus is not on that element.

It has 3 values, “off”, “polite” and “assertive” . The politeness levels differ from each other in the order of updates. SR completes what it’s reading and then announce the changes when “polite” is used and interrupts whatever the SR is reading to announce the change when using “assertive”.

By default aria-live attribute value is assertive.

<div>
{errorText &&
<span
id={errorId}
aria-live=“polite”
aria-atomic=“true”
>
{errorText}
</span>
}
</div>

aria-live is accompanied by two other attributes — aria-atomic and aria-relavant.

aria-atomic ensures that the element is read as a whole even if the changes are done to a part of the element. By default its value is true.

aria-relevant decides when to announce aria-live region updates. It can take multiple space delimited string values of“additions”, “text” or “removals” otherwise it can also be “off” .

By default aria-relevant has value “additions text” which means announce the changes if there are any new DOM node additions or text updates.

Note: The aria-live regions must always be present in the DOM before they are announced otherwise they don’t always work for all assistive technologies.

If the element is added dynamically or the aria-live attribute is added dynamically then, there is a chance that they may not be announced.

<div>
<FormInput
errorText=“Please provide a password”
/>
</div>
...
<div>
{errorText &&
<span
id={errorId}
aria-live=“polite”
aria-atomic=“true”
>
{!isValid ? errorText: “”} // Don’t wanna display error message if not present.
</span>
}
</div>

14. Focus on navigation

If using React and hooks useRef hook can be used to associate an element and bring focus on it when navigated to it. For example a simple navigation list sets focus to the selected nav item is shown as below:

useEffect(() => {
if (activeRef.current) {
activeRef.current.focus();
}
}, []);
<nav aria-label="Main menu">
<ul>
{items.map((item, index) => (
<li key={`nav-link-${item.to}-${index}`}>
<Link
to={item.to}
title={item.title}
ref={window.location.pathname === item.to ? activeRef : undefined}
>
{item.title}
</Link>
</li>
</ul>
</nav>

Tips:

  • Current focus on the page can be found directly by using document.activeElement in dev-tools.

15. Hiding semantics with Presentation roles

Sometimes we want to hide elements from being read by the assistive technologies because it may be not convey semantic meaning of the content. To accomplish this, roles called “presentation” or “none” are used. For e.g. a tablist built using ul & li elements where the li elements are only presentational while the a inside each li is focusable as shown below:

<ul role="tablist">   
<li role="presentation">
<a role="tab" href="#">Tab 1</a>
</li>
<li role="presentation">
<a role="tab" href="#">Tab 2</a>
</li>
<li role="presentation">
<a role="tab" href="#">Tab 3</a>
</li>
</ul>

Tips:

  • Browsers can ignore this role if the element is focusable or has a tabindex attribute.
  • If this role is applied to an element which doesn’t have a condition that the browser can ignore it, any ARIA properties and states associated with that element are hidden from assistive technology.
  • The roles, states and properties of the descendant elements remain visible to the assistive technologies unless the descendant context requires the presentation role. e.g in case of ul/ol, li inherits the presentation role while the nested elements inside li are still accessible.

--

--

Software Engineer @ Microsoft | Full-Stack | Data viz| Node.js | React | GraphQL | UI/UX