Simple Patterns for Separation (Better Than Color Alone)

Color is pretty good for separating things. That’s what your basic pie chart is, isn’t it? You tell the slices apart by color. With enough color contrast, you might be OK, but you might be even better off (particularly where accessibility is concerned) using patterns, or a combination.

Patrick Dillon tackled the Pie Chart thing

Enhancing Charts With SVG Patterns:

When one of the slices is filled with something more than color, it’s easier to figure out [who the Independents are]:

See the Pen Political Party Affiliation – #2 by Patrick Dillon (@pdillon) on CodePen.

Filling a pie slice with a pattern is not a common charting library feature (yet), but if your library of choice is SVG-based, you are free to implement SVG patterns.

As in, literally a <pattern /> in SVG!

Here’s a simple one for horizontal lines:

<pattern id="horzLines" width="8" height="4" patternUnits="userSpaceOnUse"> <line x1="0" y1="0" x2="8" y2="0" style="stroke:#999;stroke-width:1.5" />

Now any SVG element can use that pattern as a fill. Even strokes. Here’s an example of mixed usage of two simple patterns:

See the Pen Simple Line Patterns by Chris Coyier (@chriscoyier) on CodePen.

That’s nice for filling SVG elements, but what about HTML elements?

Irene Ros created Pattern Fills that are SVG based, but usable in CSS also.

Using SVG Patterns as Fills:

There are several ways to use Pattern Fills:

  • You can use the patterns.css file that contains all the current patterns. That will only work for non-SVG elements.

  • You can use individual patterns, but copying them from the sample pages. CSS class definitions can be found here and SVG pattern defs can be found here

  • You can add your own patterns or modify mine! The conversion process from SVG document to pattern is very tedious. The purpose of the pattern fills toolchain is to simplify this process. You can clone the repo, run npm install and grunt dev to get a local server going. After that, any changes or additions to the src/patterns/**/* files will be automatically picked up and will re-render the CSS file and the sample pages. If you make new patterns, send them over in a pull request!

Here’s me applying them to SVG elements (but could just as easily be applied to HTML elements):

See the Pen Practical Patterns by Chris Coyier (@chriscoyier) on CodePen.

The CSS usage is as base64 data URLs though, so once they are there they aren’t super duper manageable/changeable.

Here’s Irene with an old timey chart, using d3:

Managing an SVG pattern in CSS

If your URL encode the SVG just right, you and plop it right into CSS and have it remain fairly managable.

See the Pen Simple Line Patterns by Chris Coyier (@chriscoyier) on CodePen.

Other Examples Combining Color

Here’s one by John Schulz:

See the Pen SVG Colored Patterns by Chris Coyier (@chriscoyier) on CodePen.

Ricardo Marimón has an example creating the pattern in d3. The pattern looks largely the same on the slices, but perhaps it’s a start to modify.

Other Pattern Sources

We rounded a bunch of them up recently!

Simple Patterns for Separation (Better Than Color Alone) is a post from CSS-Tricks

How to Disable Links

The topic of disabling links popped up at my work the other day. Somehow, a “disabled” anchor style was added to our typography styles last year when I wasn’t looking. There is a problem though: there is no real way to disable an <a> link (with a valid href attribute) in HTML. Not to mention, why would you even want to? Links are the basis of the web.

At a certain point, it looked like my co-workers were not going to accept this fact, so I started thinking of how this could be accomplished. Knowing that it would take a lot, I wanted to prove that it was not worth the effort and code to support such an unconventional interaction, but I feared that by showing it could be done they would ignore all my warnings and just use my example as proof that it was OK. This hasn’t quite shaken out for me yet, but I figured we could go through my research.

First, things first:

Just don’t do it.

A disabled link is not a link, it’s just text. You need to rethink your design if it calls for disabling a link.

Bootstrap has examples of applying the .disabled class to anchor tags, and I hate them for it. At least they mention that the class only provides a disabled style, but this is misleading. You need to do more than just make a link look disabled if you really want to disable it.

Surefire way: remove the href

If you have decided that you are going to ignore my warning and proceed with disabling a link, then removing the href attribute is the best way I know how.

Straight from the official Hyperlink spec:

The href attribute on a and area elements is not required; when those elements do not have href attributes they do not create hyperlinks.

An easier to understand definition from MDN:

This attribute may be omitted (as of HTML5) to create a placeholder link. A placeholder link resembles a traditional hyperlink, but does not lead anywhere.

Here is basic JavaScript code to set and remove the href attribute:

/* * Use your preferred method of targeting a link * * document.getElementById('MyLink'); * document.querySelector('.link-class'); * document.querySelector('[href=""]'); */
// "Disable" link by removing the href property
link.href = '';
// Enable link by setting the href property
link.href = '';

Styling this via CSS is also pretty straightforward:

a { /* Disabled link styles */
a:link, a:visited { /* or a[href] */ /* Enabled link styles */

That’s all you need to do!

That’s not enough, I want something more complex so that I can look smarter!

If you just absolutely have to over-engineer some extreme solution, here are some things to consider. Hopefully, you will take heed and recognize that what I am about to show you is not worth the effort.

First, we need to style our link so that it looks disabled.

.isDisabled { color: currentColor; cursor: not-allowed; opacity: 0.5; text-decoration: none;
<a class="isDisabled" href="">Disabled Link</a>

Setting color to currentColor should reset the font color back to your normal, non-link text color. I am also setting the mouse cursor to not-allowed to display a nice indicator on hover that the normal action is not allowed. Already, we have left out non-mouse users that can’t hover, mainly touch and keyboard, so they won’t get this indication. Next the opacity is cut to half. According to WCAG, disabled elements do not need to meet color contrast guidelines. I think this is very risky since it’s basically plain text at this point, and dropping the opacity in half would make it very hard to read for users with low-vision, another reason I hate this. Lastly, the text decoration underline is removed as this is usually the best indicator something is a link. Now this looks like a disabled link!

But it’s not really disabled! A user can still click/tap on this link. I hear you screaming about pointer-events.

.isDisabled { ... pointer-events: none;

Ok, we are done! Disabled link accomplished! Except, it’s only really disabled for mouse users clicking and touch users tapping. What about browsers that don’t support pointer-events? According to caniuse, this is not supported for Opera Mini and IE<11. IE11 and Edge actually don't support pointer-events unless display is set to block or inline-block. Also, setting pointer-events to none overwrites our nice not-allowed cursor, so now mouse users will not get that additional visual indication that the link is disabled. This is already starting to fall apart. Now we have to change our markup and CSS…

.isDisabled { cursor: not-allowed; opacity: 0.5;
.isDisabled > a { color: currentColor; display: inline-block; /* For IE11/ MS Edge bug */ pointer-events: none; text-decoration: none;
<span class="isDisabled"><a href="">Disabled Link</a></span>

Wrapping the link in a <span> and adding the isDisabled class gives us half of our disabled visual style. A nice side-affect here is that the disabled class is now generic and can be used on other elements, like buttons and form elements. The actual anchor tag now has the pointer-events and text-decoration set to none.

What about keyboard users? Keyboard users will use the ENTER key to activate links. pointer-events are only for pointers, there is no keyboard-events. We also need to prevent activation for older browsers that don’t support pointer-events. Now we have to introduce some JavaScript.

Bring in the JavaScript

// After using preferred method to target link
link.addEventListener('click', function (event) { if (this.parentElement.classList.contains('isDisabled')) { event.preventDefault(); }

Now our link looks disabled and does not respond to activation via clicks, taps, and the ENTER key. But we are still not done! Screen reader users have no way of knowing that this link is disabled. We need to describe this link as being disabled. The disabled attribute is not valid on links, but we can use aria-disabled="true".

<span class="isDisabled"><a href="" aria-disabled="true">Disabled Link</a></span>

Now I am going to take this opportunity to style the link based on the aria-disabled attribute. I like using ARIA attributes as hooks for CSS because having improperly styled elements is an indicator that important accessibility is missing.

.isDisabled { cursor: not-allowed; opacity: 0.5;
a[aria-disabled="true"] { color: currentColor; display: inline-block; /* For IE11/ MS Edge bug */ pointer-events: none; text-decoration: none;

Now our links look disabled, act disabled, and are described as disabled.

Unfortunately, even though the link is described as disabled, some screen readers (JAWS) will still announce this as clickable. It does that for any element that has a click listener. This is because of developer tendency to make non-interactive elements like div and span as pseudo-interactive elements with a simple listener. Nothing we can do about that here. Everything we have done to remove any indication that this is a link is foiled by the assistive technology we were trying to fool, ironically because we have tried to fool it before.

But what if we moved the listener to the body?

document.body.addEventListener('click', function (event) { // filter out clicks on any other elements if ( == 'A' &&'aria-disabled') == 'true') { event.preventDefault(); }

Are we done? Well, not really. At some point we will need to enable these links so we need to add additional code that will toggle this state/behavior.

function disableLink(link) {
// 1. Add isDisabled class to parent span link.parentElement.classList.add('isDisabled');
// 2. Store href so we can add it later link.setAttribute('data-href', link.href);
// 3. Remove href link.href = '';
// 4. Set aria-disabled to 'true' link.setAttribute('aria-disabled', 'true');
function enableLink(link) {
// 1. Remove 'isDisabled' class from parent span link.parentElement.classList.remove('isDisabled');
// 2. Set href link.href = link.getAttribute('data-href');
// 3. Remove 'aria-disabled', better than setting to false link.removeAttribute('aria-disabled');

That’s it. We now have a disabled link that is visually, functionally, and semantically disabled for all users. It only took 10 lines of CSS, 15 lines of JavaScript (including 1 listener on the body), and 2 HTML elements.

Seriously folks, just don’t do it.

How to Disable Links is a post from CSS-Tricks

Accessible Web Apps with React, TypeScript, and AllyJS

Accessibility is an aspect of web development that is often overlooked. I would argue that it is as vital as overall performance and code reusability. We justify our endless pursuit of better performance and responsive design by citing the users, but ultimately these pursuits are done with the user’s device in mind, not the user themselves and their potential disabilities or restrictions.

A responsive app should be one that delivers its content based on the needs of the user, not only their device.

Luckily, there are tools to help alleviate the learning curve of accessibility-minded development. For example, GitHub recently released their accessibility error scanner, AccessibilityJS and Deque has aXe. This article will focus on a different one: Ally.js, a library simplifying certain accessibility features, functions, and behaviors.

One of the most common pain points regarding accessibility is dialog windows.

There’re a lot of considerations to take in terms of communicating to the user about the dialog itself, ensuring ease of access to its content, and returning to the dialog’s trigger upon close.

A demo on the Ally.js website addresses this challenge which helped me port its logic to my current project which uses React and TypeScript. This post will walk through building an accessible dialog component.

Demo of accessible dialog window using Ally.js within React and TypeScript

View the live demo

Project Setup with create-react-app

Before getting into the use of Ally.js, let’s take a look at the initial setup of the project. The project can be cloned from GitHub or you can follow along manually. The project was initiated using create-react-app in the terminal with the following options:

create-react-app my-app --scripts-version=react-scripts-ts

This created a project using React and ReactDOM version 15.6.1 along with their corresponding @types.

With the project created, let’s go ahead and take a look at the package file and project scaffolding I am using for this demo.

Project architecture and package.json file

As you can see in the image above, there are several additional packages installed but for this post we will ignore those related to testing and focus on the primary two, ally.js and babel-polyfill.

Let’s install both of these packages via our terminal.

yarn add ally.js --dev && yarn add babel-polyfill --dev

For now, let’s leave `/src/index.tsx` alone and hop straight into our App container.

App Container

The App container will handle our state that we use to toggle the dialog window. Now, this could also be handled by Redux but that will be excluded in lieu of brevity.

Let’s first define the state and toggle method.

interface AppState { showDialog: boolean;
} class App extends React.Component<{}, AppState> { state: AppState; constructor(props: {}) { super(props); this.state = { showDialog: false }; } toggleDialog() { this.setState({ showDialog: !this.state.showDialog }); }

The above gets us started with our state and the method we will use to toggle the dialog. Next would be to create an outline for our render method.

class App extends React.Component<{}, AppState> { ... render() { return ( <div className="site-container"> <header> <h1>Ally.js with React &amp; Typescript</h1> </header> <main className="content-container"> <div className="field-container"> <label htmlFor="name-field">Name:</label> <input type="text" id="name-field" placeholder="Enter your name" /> </div> <div className="field-container"> <label htmlFor="food-field">Favourite Food:</label> <input type="text" id="food-field" placeholder="Enter your favourite food" /> </div> <div className="field-container"> <button className='btn primary' tabIndex={0} title='Open Dialog' onClick={() => this.toggleDialog()} > Open Dialog </button> </div> </main> </div> ); }

Don’t worry much about the styles and class names at this point. These elements can be styled as you see fit. However, feel free to clone the GitHub repo for the full styles.

At this point we should have a basic form on our page with a button that when clicked toggles our showDialog state value. This can be confirmed by using React’s Developer Tools.

So let’s now have the dialog window toggle as well with the button. For this let’s create a new Dialog component.

Dialog Component

Let’s look at the structure of our Dialog component which will act as a wrapper of whatever content (children) we pass into it.

interface Props { children: object; title: string; description: string; close(): void;
} class Dialog extends React.Component<Props> { dialog: HTMLElement | null; render() { return ( <div role="dialog" tabIndex={0} className="popup-outer-container" aria-hidden={false} aria-labelledby="dialog-title" aria-describedby="dialog-description" ref={(popup) => { this.dialog = popup; } } > <h5 id="dialog-title" className="is-visually-hidden" > {this.props.title} </h5> <p id="dialog-description" className="is-visually-hidden" > {this.props.description} </p> <div className="popup-inner-container"> <button className="close-icon" title="Close Dialog" onClick={() => { this.props.close(); }} > × </button> {this.props.children} </div> </div> ); }

We begin this component by creating the Props interface. This will allow us to pass in the dialog’s title and description, two important pieces for accessibility. We will also pass in a close method, which will refer back to the toggleDialog method from the App container. Lastly, we create the functional ref to the newly created dialog window to be used later.

The following styles can be applied to create the dialog window appearance.

.popup-outer-container { align-items: center; background: rgba(0, 0, 0, 0.2); display: flex; height: 100vh; justify-content: center; padding: 10px; position: absolute; width: 100%; z-index: 10;
} .popup-inner-container { background: #fff; border-radius: 4px; box-shadow: 0px 0px 10px 3px rgba(119, 119, 119, 0.35); max-width: 750px; padding: 10px; position: relative; width: 100%;
} .popup-inner-container:focus-within { outline: -webkit-focus-ring-color auto 2px;
} .close-icon { background: transparent; color: #6e6e6e; cursor: pointer; font: 2rem/1 sans-serif; position: absolute; right: 20px; top: 1rem;

Now, let’s tie this together with the App container and then get into Ally.js to make this dialog window more accessible.

App Container

Back in the App container, let’s add a check inside of the render method so any time the showDialog state updates, the Dialog component is toggled.

class App extends React.Component<{}, AppState> { ... checkForDialog() { if (this.state.showDialog) { return this.getDialog(); } else { return false; } } getDialog() { return ( <Dialog title="Favourite Holiday Dialog" description="Add your favourite holiday to the list" close={() => { this.toggleDialog(); }} > <form className="dialog-content"> <header> <h1 id="dialog-title">Holiday Entry</h1> <p id="dialog-description">Please enter your favourite holiday.</p> </header> <section> <div className="field-container"> <label htmlFor="within-dialog">Favourite Holiday</label> <input id="within-dialog" /> </div> </section> <footer> <div className="btns-container"> <Button type="primary" clickHandler={() => { this.toggleDialog(); }} msg="Save" /> </div> </footer> </form> </Dialog> ); } render() { return ( <div className="site-container"> {this.checkForDialog()} ... ); }

What we’ve done here is add the methods checkForDialog and getDialog.

Inside of the render method, which runs any time the state updates, there is a call to run checkForDialog. So upon clicking the button, the showDialog state will update, causing a re-render, calling checkForDialog again. Only now, showDialog is true, triggering getDialog. This method returns the Dialog component we just built to be rendered onto the screen.

The above sample includes a Button component that has not been shown.

Now, we should have the ability to open and close our dialog. So let’s take a look at what problems exist in terms of accessibility and how we can address them using Ally.js.

Using only your keyboard, open the dialog window and try to enter text into the form. You’ll notice that you must tab through the entire document to reach the elements within the dialog. This is a less-than-ideal experience. When the dialog opens, our focus should be the dialog  –  not the content behind it. So let’s look at our first use of Ally.js to begin remedying this issue.


Ally.js is a library providing various modules to help simplify common accessibility challenges. We will be using four of these modules for the Dialog component.

The .popup-outer-container acts as a mask that lays over the page blocking interaction from the mouse. However, elements behind this mask are still accessible via keyboard, which should be disallowed. To do this the first Ally module we’ll incorporate is maintain/disabled. This is used to disable any set of elements from being focussed via keyboard, essentially making them inert.

Unfortunately, implementing Ally.js into a project with TypeScript isn’t as straightforward as other libraries. This is due to Ally.js not providing a dedicated set of TypeScript definitions. But no worries, as we can declare our own modules via TypeScript’s types files.

In the original screenshot showing the scaffolding of the project, we see a directory called types. Let’s create that and inside create a file called `global.d.ts`.

Inside of this file let’s declare our first Ally.js module from the esm/ directory which provides ES6 modules but with the contents of each compiled to ES5. These are recommended when using build tools.

declare module 'ally.js/esm/maintain/disabled';

With this module now declared in our global types file, let’s head back into the Dialog component to begin implementing the functionality.

Dialog Component

We will be adding all the accessibility functionality for the Dialog to its component to keep it self-contained. Let’s first import our newly declared module at the top of the file.

import Disabled from 'ally.js/esm/maintain/disabled';

The goal of using this module will be once the Dialog component mounts, everything on the page will be disabled while filtering out the dialog itself.

So let’s use the componentDidMount lifecycle hook for attaching any Ally.js functionality.

interface Handle { disengage(): void;
} class Dialog extends React.Component<Props, {}> { dialog: HTMLElement | null; disabledHandle: Handle; componentDidMount() { this.disabledHandle = Disabled({ filter: this.dialog, }); } componentWillUnmount() { this.disabledHandle.disengage(); } ...

When the component mounts, we store the Disabled functionality to the newly created component property disableHandle. Because there are no defined types yet for Ally.js we can create a generic Handle interface containing the disengage function property. We will be using this Handle again for other Ally modules, hence keeping it generic.

By using the filter property of the Disabled import, we’re able to tell Ally.js to disable everything in the document except for our dialog reference.

Lastly, whenever the component unmounts we want to remove this behaviour. So inside of the componentWillUnmount hook, we disengage() the disableHandle.

We will now follow this same process for the final steps of improving the Dialog component. We will use the additional Ally modules:

  • maintain/tab-focus
  • query/first-tabbable
  • when/key

Let’s update the `global.d.ts` file so it declares these additional modules.

declare module 'ally.js/esm/maintain/disabled';
declare module 'ally.js/esm/maintain/tab-focus';
declare module 'ally.js/esm/query/first-tabbable';
declare module 'ally.js/esm/when/key';

As well as import them all into the Dialog component.

import Disabled from 'ally.js/esm/maintain/disabled';
import TabFocus from 'ally.js/esm/maintain/tab-focus';
import FirstTab from 'ally.js/esm/query/first-tabbable';
import Key from 'ally.js/esm/when/key';

Tab Focus

After disabling the document with the exception of our dialog, we now need to restrict tabbing access further. Currently, upon tabbing to the last element in the dialog, pressing tab again will begin moving focus to the browser’s UI (such as the address bar). Instead, we want to leverage tab-focus to ensure the tab key will reset to the beginning of the dialog, not jump to the window.

class Dialog extends React.Component<Props> { dialog: HTMLElement | null; disabledHandle: Handle; focusHandle: Handle; componentDidMount() { this.disabledHandle = Disabled({ filter: this.dialog, }); this.focusHandle = TabFocus({ context: this.dialog, }); } componentWillUnmount() { this.disabledHandle.disengage(); this.focusHandle.disengage(); } ...

We follow the same process here as we did for the disabled module. Let’s create a focusHandle property which will assume the value of the TabFocus module import. We define the context to be the active dialog reference on mount and then disengage() this behaviour, again, when the component unmounts.

At this point, with a dialog window open, hitting tab should cycle through the elements within the dialog itself.

Now, wouldn’t it be nice if the first element of our dialog was already focused upon opening?

First Tab Focus

Leveraging the first-tabbable module, we are able to set focus to the first element of the dialog window whenever it mounts.

class Dialog extends React.Component<Props> { dialog: HTMLElement | null; disabledHandle: Handle; focusHandle: Handle; componentDidMount() { this.disabledHandle = Disabled({ filter: this.dialog, }); this.focusHandle = TabFocus({ context: this.dialog, }); let element = FirstTab({ context: this.dialog, defaultToContext: true, }); element.focus(); } ...

Within the componentDidMount hook, we create the element variable and assign it to our FirstTab import. This will return the first tabbable element within the context that we provide. Once that element is returned, calling element.focus() will apply focus automatically.

Now, that we have the behavior within the dialog working pretty well, we want to improve keyboard accessibility. As a strict laptop user myself (no external mouse, monitor, or any peripherals) I tend to instinctively press esc whenever I want to close any dialog or popup. Normally, I would write my own event listener to handle this behavior but Ally.js provides the when/key module to simplify this process as well.

class Dialog extends React.Component<Props> { dialog: HTMLElement | null; disabledHandle: Handle; focusHandle: Handle; keyHandle: Handle; componentDidMount() { this.disabledHandle = Disabled({ filter: this.dialog, }); this.focusHandle = TabFocus({ context: this.dialog, }); let element = FirstTab({ context: this.dialog, defaultToContext: true, }); element.focus(); this.keyHandle = Key({ escape: () => { this.props.close(); }, }); } componentWillUnmount() { this.disabledHandle.disengage(); this.focusHandle.disengage(); this.keyHandle.disengage(); } ...

Again, we provide a Handle property to our class which will allow us to easily bind the esc functionality on mount and then disengage() it on unmount. And like that, we’re now able to easily close our dialog via the keyboard without necessarily having to tab to a specific close button.

Lastly (whew!), upon closing the dialog window, the user’s focus should return to the element that triggered it. In this case, the Show Dialog button in the App container. This isn’t built into Ally.js but a recommended best practice that, as you’ll see, can be added in with little hassle.

class Dialog extends React.Component<Props> { dialog: HTMLElement | null; disabledHandle: Handle; focusHandle: Handle; keyHandle: Handle; focusedElementBeforeDialogOpened: HTMLInputElement | HTMLButtonElement; componentDidMount() { if (document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLButtonElement) { this.focusedElementBeforeDialogOpened = document.activeElement; } this.disabledHandle = Disabled({ filter: this.dialog, }); this.focusHandle = TabFocus({ context: this.dialog, }); let element = FirstTab({ context: this.dialog, defaultToContext: true, }); this.keyHandle = Key({ escape: () => { this.props.close(); }, }); element.focus(); } componentWillUnmount() { this.disabledHandle.disengage(); this.focusHandle.disengage(); this.keyHandle.disengage(); this.focusedElementBeforeDialogOpened.focus(); } ...

What has been done here is a property, focusedElementBeforeDialogOpened, has been added to our class. Whenever the component mounts, we store the current activeElement within the document to this property.

It’s important to do this before we disable the entire document or else document.activeElement will return null.

Then, like we had done with setting focus to the first element in the dialog, we will use the .focus() method of our stored element on componentWillUnmount to apply focus to the original button upon closing the dialog. This functionality has been wrapped in a type guard to ensure the element supports the focus() method.

Now, that our Dialog component is working, accessible, and self-contained we are ready to build our App. Except, running yarn test or yarn build will result in an error. Something to this effect:

[path]/node_modules/ally.js/esm/maintain/disabled.js:21 import nodeArray from '../util/node-array'; ^^^^^^ SyntaxError: Unexpected token import

Despite Create React App and its test runner, Jest, supporting ES6 modules, an issue is still caused with the ESM declared modules. So this brings us to our final step of integrating Ally.js with React, and that is the babel-polyfill package.

All the way in the beginning of this post (literally, ages ago!), I showed additional packages to install, the second of which being babel-polyfill. With this installed, let’s head to our app’s entry point, in this case ./src/index.tsx.


At the very top of this file, let’s import babel-polyfill. This will emulate a full ES2015+ environment and is intended to be used in an application rather than a library/tool.

import 'babel-polyfill';

With that, we can return to our terminal to run the test and build scripts from create-react-app without any error.

Demo of accessible dialog window using Ally.js within React and TypeScript

View the live demo

Now that Ally.js is incorporated into your React and TypeScript project, more steps can be taken to ensure your content can be consumed by all users, not just all of their devices.

For more information on accessibility and other great resources please visit these resources:

  • Accessible Web Apps with React, TypeScript & Ally.js on Github
  • Start Building Accessible Web Applications Today
  • HTML Codesniffer
  • Web Accessibility Best Practices
  • Writing CSS with Accessibility in Mind
  • Accessibility Checklist

Accessible Web Apps with React, TypeScript, and AllyJS is a post from CSS-Tricks

ARIA is Spackle, Not Rebar

Much like their physical counterparts, the materials we use to build websites have purpose. To use them without understanding their strengths and limitations is irresponsible. Nobody wants to live in an poorly-built house. So why are poorly-built websites acceptable?

In this post, I’m going to address WAI-ARIA, and how misusing it can do more harm than good.

Materials as technology

In construction, spackle is used to fix minor defects on interiors. It is a thick paste that dries into a solid surface that can be sanded smooth and painted over. Most renters become acquainted with it when attempting to get their damage deposit back.

Rebar is a lattice of steel rods used to reinforce concrete. Every modern building uses it—chances are good you’ll see it walking past any decent-sized construction site.

Technology as materials

HTML is the rebar-reinforced concrete of the web. To stretch the metaphor, CSS is the interior and exterior decoration, and JavaScript is the wiring and plumbing.

Every tag in HTML has what is known as native semantics. The act of writing an HTML element programmatically communicates to the browser what that tag represents. Writing a button tag explicitly tells the browser, “This is a button. It does buttony things.”

The reason this is so important is that assistive technology hooks into native semantics and uses it to create an interface for navigation. A page not described semantically is a lot like a building without rooms or windows: People navigating via a screen reader have to wander around aimlessly in the dark and hope they stumble onto what they need.

ARIA stands for Accessible Rich Internet Applications and is a relatively new specification developed to help assistive technology better communicate with dynamic, JavaScript-controlled content. It is intended to supplement existing semantic attributes by providing enhanced interactivity and context to screen readers and other assistive technology.

Using spackle to build walls

A concerning trend I’ve seen recently is the blind, mass-application of ARIA. It feels like an attempt by developers to conduct accessibility compliance via buckshot—throw enough of something at a target trusting that you’ll eventually hit it.

Unfortunately, there is a very real danger to this approach. Misapplied ARIA has the potential to do more harm than good.

The semantics inherent in ARIA means that when applied improperly it can create a discordant, contradictory mess when read via screen reader. Instead of hearing, “This is a button. It does buttony things.”, people begin to hear things along the lines of, “This is nothing, but also a button. But it’s also a deactivated checkbox that is disabled and it needs to shout that constantly.”

If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.
– First rule of ARIA use

In addition, ARIA is a new technology. This means that browser support and behavior is varied. While I am optimistic that in the future the major browsers will have complete and unified support, the current landscape has gaps and bugs.

Another important consideration is who actually uses the technology. Compliance isn’t some purely academic vanity metric we’re striving for. We’re building robust systems for real people that allow them to get what they want or need with as little complication as possible. Many people who use assistive technology are reluctant to upgrade for fear of breaking functionality. Ever get irritated when your favorite program redesigns and you have to re-learn how to use it? Yeah.

The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect.
– Tim Berners-Lee

It feels disingenuous to see the benefits of the accessible by default. For better or for worse, we are free to do what we want to it after that.

The fix

This isn’t to say we should completely avoid using ARIA. When applied with skill and precision, it can turn a confusing or frustrating user experience into an intuitive and effortless one, with far fewer brittle hacks and workarounds.

A little goes a long way. Before considering other options, start with markup that semantically describes the content it is wrapping. Test extensively, and only apply ARIA if deficiencies between HTML’s native semantics and JavaScript’s interactions arise.

Development teams will appreciate the advantage of terse code that’s easier to maintain. Savvy developers will use a CSS-Trick™ and leverage CSS attribute selectors to create systems where visual presentation is tied to semantic meaning.

[aria-invalid] { border: 4px dotted #f64100;


Here are a few of the more common patterns I’ve seen recently, and why they are problematic. This doesn’t mean these are the only kinds of errors that exist, but it’s a good primer on recognizing what not to do:

<li role="listitem">Hold the Bluetooth button on the speaker for three seconds to make the speaker discoverable</li>

The role is redundant. The native semantics of the li element already describe it as a list item.

<p role="command">Type CTRL+P to print

command is an Abstract Role. They are only used in ARIA to help describe its taxonomy. Just because an ARIA attribute seems like it is applicable doesn’t mean it necessarily is. Additionally, the kbd tag could be used on “CTRL” and “P” to more accurately describe the keyboard command.

<div role="button" class="button">Link to device specifications</div>

Failing to use a button tag runs the risk of not accommodating all the different ways a user can interact with a button and how the browser responds. In addition, the a tag should be used for links.

<body aria-live="assertive" aria-atomic="true">

Usually the intent behind something like this is to expose updates to the screen reader user. Unfortunately, when scoped to the body tag, any page change—including all JS-related updates—are announced immediately. A setting of assertive on aria-live also means that each update interrupts whatever it is the user is currently doing. This is a disastrous experience, especially for single page apps.

<div aria-checked="true"></div>

You can style a native checkbox element to look like whatever you want it to. Better support! Less work!

<div role="link" tabindex="40"> Link text

Yes, it’s actual production code. Where to begin? First, never use a tabindex value greater than 0. Secondly, the title attribute probably does not do what you think it does. Third, the anchor tag should have a destination—links take you places, after all. Fourth, the role of link assigned to a div wrapping an a element is entirely superfluous.

<h2 class="h3" role="heading" aria-level="1">How to make a perfect soufflé every time</h2>

Credit is where credit’s due: Nicolas Steenhout outlines the issues for this one.

Do better

Much like content, markup shouldn’t be an afterthought when building a website. I believe most people are genuinely trying to do their best most of the time, but wielding a technology without knowing its implications is dangerous and irresponsible.

I’m usually more of a honey-instead-of-vinegar kind of person when I try to get people to practice accessibility, but not here. This isn’t a soft sell about the benefits of developing and designing with an accessible, inclusive mindset. It’s a post about doing your job.

Every decision a team makes affects a site’s accessibility.
– Laura Kalbag

Get better at authoring

Learn about the available HTML tags, what they describe, and how to best use them. Same goes for ARIA. Give your page template semantics the same care and attention you give your JavaScript during code reviews.

Get better at testing

There’s little excuse to not incorporate a screen reader into your testing and QA process. NVDA is free. macOS, Windows, iOS and Android all come with screen readers built in. Some nice people have even written guides to help you learn how to use them.

Automated accessibility testing is a huge boon, but it also isn’t a silver bullet. It won’t report on what it doesn’t know to report, meaning it’s up to a human to manually determine if navigating through the website makes sense. This isn’t any different than other usability testing endeavors.

Build better buildings

Universal Design teaches us that websites, like buildings, can be both beautiful and accessible. If you’re looking for a place to start, here are some resources:

  • A Book Apart: Accessibility for Everyone, by Laura Kalbag
  • Intro to ARIA and Start Building Accessible Web Applications Today, by Marcy Sutton
  • Google Developers: Introduction to ARIA, by Meggin Kearney, Dave Gash, and Alice Boxhall
  • YouTube: A11ycasts with Rob Dodson, by Rob Dodson
  • W3C: WAI-ARIA Authoring Practices 1.1
  • W3C: Using ARIA
  • Zomigi: Videos of screen readers using ARIA
  • Inclusive Components, by Heydon Pickering
  • HTML5 Accessibility
  • The American Foundation for the Blind: Improving Your Website’s Accessibility
  • Designing for All: 5 Ways to Make Your Next Website Design More Accessible, by Carie Fisher
  • Accessible Interface Design, by Nick Babich

ARIA is Spackle, Not Rebar is a post from CSS-Tricks