Ingredients for Better Accessibility — Part One: The Basics
At Galvanize we have built a UI library of React components (Paprika) to implement our design system (Starling). One of the core principles of this library is to be usable — which implies usable for everyone. To fulfill this vision we have committed to making our components as accessible as possible.
As much as possible, we will bake the accessibility implementation right into the component so that anyone consuming that component will not need to know the details. This is not always possible, so in many cases we strive to provide components that enable and facilitate the development of accessible web applications.
In this series I’d like to take a closer look at some of the specific choices we’ve made in our component library to achieve this goal. We are proud of the work we’ve done to create a UI library with a spirit of inclusiveness and hope that sharing these details can advance the state of accessibility in the web development field.
Fake Buttons
It is universally recommended by accessibility experts to use a semantic <button>
HTML element any time you need to use a button on a web page. Simply adding onClick
to a plain old <div>
is a terrible idea!
- It will not be recognized as a button by assistive technology, like screen readers (WCAG 1.3.6, 4.1.2).
- It will not receive keyboard focus when the user “tabs” through the page (WCAG 2.1.1, 2.4.7).
- It cannot be activated when the user presses
enter
orspace
on the keyboard (WCAG 2.1.1).
Using a native <button>
will work better and definitely be more accessible than a fake <div onClick={handleClick}>
button.
But a <button>
element looks like a button. What if you want something to be clickable, but you don’t want it to look like a button? Custom UI widgets may well need clickable elements that do not look like standard browser buttons.
What if you are maintaining an existing web site or building new features for an old web application that includes global CSS targeting the <button>
element? Then you will spend a lot of time and effort fighting with those CSS rules. Ideally you would remove the offending global CSS, but that is an unrealistically large amount of work and testing in many cases. For a UI component used in multiple legacy web applications, this is not a realistic solution.
That is why there is a <RawButton>
component in Paprika.
The <RawButton>
disregards the conventional wisdom of using a <button>
and renders a raw, unstyled <span>
with all the functionality and accessibility implemented from scratch.
In addition, the Paprika <Button>
component includes an isSemantic
prop that will allow styled button components to be rendered as either a native <button>
element or a raw, accessible <span>
that is resilient to global CSS. For new applications, we still prefer to leave isSemantic=true
(the default) but using isSemantic=false
has the same visual and functional result.

As a bonus, since we are using styled-components to implement the <RawButton>
, and because we relay any additional props/attributes passed to the component down to the underlying root node, it’s trivial to render the <RawButton>
as any kind of element. For example, if a table row element needs to be clickable, it may be necessary to apply click handlers directly to the <tr>
because a <span>
cannot fulfill the role of a table row. With Paprika we can simply do this:
<RawButton as=”td” onClick={handleClick}>Vegan gastropub</RawButton>
As simple as the <RawButton>
component is, it has been an extraordinarily versatile and effective tool in building accessible compound components and features that are resilient to global styling in legacy applications.
Headings
Formatting a web page with semantic headings is important for accessibility compliance (WCAG 1.3.1, 2.4.6, 2.4.10) but these success criteria only require that headings are used, not that they conform to a proper hierarchy. Maintaining a proper heading structure on the page that does not skip heading levels is a highly encouraged best practice, and will be flagged by accessibility testing tools like Deque’s axe.
There is a good reason for this too — users who depend on screen readers make extensive use of headings to scan a page quickly and find the information of interest. In a survey by WebAIM, over 86% of users responded that heading levels are useful for navigating a web page. By far the most common strategy users employ when trying to find information on a lengthy page is to navigate by headings.
The visual design of a page does not always conform rigidly to this hierarchy — a heading that should be level two (<h2>
) may be styled to look smaller and more subtle, like what a level five heading would look like. It’s fine for a heading to look smaller, as long as the heading actually is an <h2>
element in the markup. This can be accomplished easily enough with CSS, but to alleviate any temptation for developers to simply use an <h5>
element instead, we have baked this capability into the API for the Paprika <Heading>
component with the a level
and a displayLevel
prop. This makes it simple to produce an <h2>
that looks like an <h5>
:
<Heading level={2} displayLevel={5}>Cold-pressed Hexagon</Heading>

Invisible Headings
There are even some cases where the visual layout bestows enough context that a heading is not necessary and in fact would clutter the design. But for a screen reader user, it is difficult to understand the context or to navigate quickly to the related content without the heading landmark. To improve the experience for users relying on a screen reader, a simple solution is to include a visually hidden heading on the page. Again, this is simple with the Paprika component:
<Heading level={2} isHidden>Gastropub skateboard</Heading>
Similarly, a very brief heading may be appropriate in the context of a visual layout, but without cues such as the proximity to related items, a heading that is too short may be ambiguous for screen reader users. In this case it’s possible to override the visual content of the <Heading>
with an aria-label
attribute that takes precedence for assistive technology. We insulate consumers of this component (and many other Paprika components) from the implementation details of ARIA by providing a generic a11yText
prop for this kind of purpose:
<Heading level={3} a11yText=”Asymmetrical lo-fi distillery vaporware”>Asymmetrical</Heading>
These features make the <Heading>
an effective tool for dramatically improving the experience for users relying on assistive technology.
Alert Messages
Pages in modern web applications are not static — there is a lot of dynamic content that changes without a page refresh. For sighted users, these changes to dynamic “live regions” on a page are obvious, but for users that rely on screen readers or other assistive technology, these changes are imperceptible unless handled deliberately.
An alert message is a typical example of this kind of dynamic content. When an asynchronous task begins, completes successfully, or terminates with an error, a notification is displayed, inline or overlaid at the top of the viewport, with the details of the event. In Paprika, there is a <Toast>
component for this purpose.

The <Toast>
component is rendered as an ARIA live region by setting the role
attribute on its root node to status
or alert
. The <Toast>
has a prop called isPolite
which is set to true
by default, meaning it has the role=status
attribute applied and its content will be announced after any current announcement finishes. If the message is urgent, then the isPolite
prop can be set to false
, the role
will be set to alert
, and the message will be read immediately. Pretty straightforward.
The catch is that assistive technology needs to be aware of the live region before the content is rendered. Ideally all live regions exist when the page first loads — then they can be observed for any changes, which will be announced when they are detected. However, with a component-based page design, the components are often being added and removed from the DOM as needed, so it is unlikely and impractical to render the live region for a component before it’s needed. Interestingly, during our initial development we found that with VoiceOver in Chrome and Safari it worked fine to mount a live region to the page with content — it would still be announced to the user. This was not the case, however, with JAWS on Windows.
The solution that has allowed us to maintain the componentized nature of the <Toast>
is to first mount and render an empty live region container, then after a very short delay (20ms) the content (children
) is rendered. This was successful in VoiceOver, JAWS, and NVDA.
Invisible Toast
Frequently with dynamic web applications, a user event will invoke the need to fetch more data. When that data is received, the UI is updated in some way with the new details. For sighted users, this is a natural process, and a notification would be redundant. But for users who rely on a screen reader there will be no indication when, or even if, that data becomes available.
For example, in a table that has hundreds of rows, the user has the option to apply search filters, such as showing only rows that contain their username. Applying that filter will result in a new set of data being retrieved from the server, a momentary loading spinner, followed by a refreshed table with only those filtered rows. Since the keyboard focus will remain on the search filter widget during the fetching event, a screen reader user will not hear any of the UI changes.
The solution for this common use case is quite simple, render a <Toast>
but don’t show it to sighted users. The <Toast>
has a kind
prop that controls how the notification looks — like a success message, error message, etc. One of the values it can have is visually-hidden
. This will apply the common accessibility technique of positioning the element well outside the viewport with CSS (but importantly not by applying display:none
or visibility:hidden
).
Since this element does exist on the page somewhere, it’s possible for the user to navigate to it while in “reading” mode with their screen reader and discover the notification or alert message out of context. To prevent this, the canAutoClose
prop on the <Toast>
can be used to automatically remove the message from the DOM after a delay that will give the screen reader enough time to finish reading the announcement.
return shouldRenderNotification
? <Toast
kind="visually-hidden"
canAutoClose
autoCloseDelay={1500}
hasCloseButton={false}
>
20 more results have been displayed.
</Toast>
: null;
This technique allows dynamic changes to be perceived in a non-visual way, with minimal development effort.
Conclusion
These basic components have made it far simpler for us to cook up more accessible features in the applications we make. They’ve also been key ingredients in many of the more complex Paprika components. All of these components are open source on GitHub and published on NPM so you are free to examine the implementation details or use them in your own project.
I have some more ideas marinating that I’d like to share about some of our more sophisticated components, like the <FormElement>
, and the focus locking technique we implement for our modal components, but will save those for another article, so stay tuned. Until next time, happy coding!