Skip to main content Accessibility Feedback

Basic accessibility mistakes I often see in audits

When I start working with new consulting clients, one of the first things I like to do is an audit of their existing site.

I’m primarily looking for performance issues, accessibility issues, and architecture issues. I put them all together into a nice report, and often helps guide a lot of the work that we do going forward.

Today, I wanted to share eight of the most common accessibility issues I see when I run these audits.

Let’s dig in!

1. Using headings for style rather than structure

Heading levels provide important semantic information about the structure of a document to screen reader users.

There should be a single h1 on the page, reserved for the main title. Subsequent headings should start with h2. Using a higher heading number conveys that the content is a subsection of the first lower heading before it in the source order.

Consider the following markup order…

<h1>All about pirates<h1>

<h2>The history of pirates</h2>

<h3>The early years</h3>

<h3>The golden age</h3>

<h2>Politics on the ship</h2>

<h2>Modern piracy</h2>
  • The main subject of the page is All about pirates.
  • The h2 headings (The history of pirates, Politics on the ship, and Modern piracy) are the main section headings.
  • The h3 headings (The early years and The golden age) come after the h2 heading The history of pirates, and before the h2 heading Politics on the ship. They represent subsections of The history of pirates.

However, I often see sites where the heading levels are used for style rather than semantic meaning, often in hero sections…

<h5>For a limited time only</h5>
<h1>Buy the super awesome widget gizmo!</h1>
<h4>The ultimate new-fangled thingamadoodad...</h4>

I presume this happens because marketing folks use a CMS to generate content, and that’s the most obvious way a non-technical/web person would find to adjust the size to what they want.

A really easy way to maintain proper heading order and get the look you want is to create heading classes that can be used to style one heading level to look like another (or a non-heading to inherit heading aesthetics).

h1, h2, h3, h4, h5, h6 {
	font-family: sans-serif;
	line-height: 1.4;
	margin: 0 0 1em;
	padding: 0;

.h1 {
	font-size: 1.5em;
	padding-top: 0.5em;

.h2 {
	font-size: 1.3125em;
	padding-top: 1em;

.h3 {
	font-size: 1.1875em;

h4, h5, h6,
.h4, .h5, .h6 {
	font-size: 1em;

Then, you can do stuff like this…

<p class="h5">For a limited time only</p>
<h1>Buy the super awesome widget gizmo!</h1>
<p class="h4">The ultimate new-fangled thingamadoodad...</p>

I often see links without an href attribute, like this…

<a class="btn" data-join>Join Now</a>

There’s no href because the link is controlled with JavaScript.

Sometimes it’s actually behaving like a button, in which case it should actually just be one. Other times, it actually navigates somewhere, but the app is an SPA so JS is handling the routing.

Either way, the browser treats a link without an href like a non-interactive element. It’s removed from the normal focus order, and doesn’t surface a pointer by default like a link would.

If the thing acts like a button, use a button element. If not, include the href and event.preventDefault() in your JavaScript.

3. Removing focus styles from focusable elements

For whatever reason, a lot of designers haaaatttteeee focus styles on things like buttons and links.

/* DON'T DO THIS! */
a, button, input, textarea, select {
	outline: 0;

That outline ring provides an important visual cue to keyboard users about where they on the page. Don’t remove it!

Focus styles don’t have to be ugly, either. My friend Eric Bailey has a great presentation on this.

4. Insufficient color contrast

Many, many brand color palettes lack sufficient color contrast between text and the background, or links and non-link text.

For people with various visual impairments, this can make reading your content and navigating your site difficult or downright impossible.

You can use a variety of tools to test and adjust the contrast of the colors on your site.

And as a general rule, always use underlines for links. Don’t rely on color alone. Underlines make it obvious. For navigation menus, you can probably disregard the underline, but for in-text links, just use underlines.

I’ve seen this pattern in multiple audits now…

<a href="/about"><button>About Us</button></a>

<!-- or... -->

<button><a href="/about">About Us</a></button>

In both cases, there was a link on the page that they wanted styled to look like a button.

The thing is, buttons and links both have semantic meaning, and convey different things. A button means “this triggers some interactive behavior on this page,”, while a link means, “you’re going to navigate somewhere.”

Using both is confusing.

Instead, you can create a class that styles links to look like buttons…

.btn {
	background-color: #f7f7f7;
	border: #808080;
	border-radius: 0.25em;
	color: #272727;
	display: inline-block;
	padding: 0.5em 0.6875em;

.btn:hover {
	background-color: #0088cc;
	border-color:  #0088cc;
	color: #ffffff;

Then, apply it to your link like this…

<a class="btn" href="/about">About Us</a>

6. Hidden elements that are still focusable

Changes to the display property cannot current be animated.

As a result, I often see interactive content hidden by shifting it off screen. Off-canvas navigation menus are a common example. The Swiper.js image carousel also does this… {
  position: absolute;
  left: -10000px;
  top: auto;
  overflow: hidden;

The problem with this technique is while the content is not visible in the UI, it can still be navigated to by keyboard.

For a sighted user who uses a keyboard to navigate the web, that means they be focused on and navigating through content that they can’t see, which can create a very confusing experience.

And for people who use a screen reader, it means they’re hearing content announced aloud that was intended to be hidden.

Use display: none or the [hidden] attribute instead.

You can alternatively use the [inert] attribute to prevent focus from being shifted into the content. This would allow you still run your animation, so long as you remove the [inert] attribute once the element is visible.

7. No accessible names

I see this a lot with with buttons and links that only use an icon and do not include text.

For example, this “favorite button” includes an SVG of a heart, but no text at all.

	<i class="icon icon-heart"></i>

Someone navigating the web with a screen reader will have no idea what that button is or does.

To fix the issue, you can add an [aria-label] attribute.

<button aria-label="Favorite This">
	<i class="icon icon-heart"></i>

The [aria-label] attribute only works on elements that have a [role], either implicit (like with button, a, and so on), or added used the [role] attribute (where appropriate).

Also, as Adrian Roselli has pointed out, [aria-label] is often not translated by automatic translation software.

As an alternative, you can use a .visually-hidden class to include content that’s exposed to screen readers but hidden visually in the HTML.

 * Visually hide an element, but leave it available for screen readers
 * @link
 * @link
 * @link
.visually-hidden {
	border: 0;
	clip: rect(0 0 0 0);
	height: 1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	white-space: nowrap;
	width: 1px;
	<i class="icon icon-heart"></i>
	<span class="visually-hidden">Favorite This</span>

Which brings us to the final mistake…

8. Missing ARIA on interactive elements

I see a lot of interactive patterns on websites that functionally work, but lack the required ARIA attributes to convey what’s going on to screen readers.

For example, consider the favorite button in the previous example.

You might style the button differently depending on whether the current items is already favorited or not. But how would someone who can’t see that style difference know that information?

The [aria-pressed] attribute indicates whether a button is currently in a “pressed” state or not.

<!-- This item is currently favorited -->
<button aria-pressed="true">
	<i class="icon icon-heart"></i>
	<span class="visually-hidden">Favorite This</span>

I see issues like this with accordion components a lot.

The triggering header should be an interactive button element, and have an [aria-expanded] property that indicates if the matching content is current shown or hidden.

<!-- This content is currently hidden -->
<h2><button aria-expanded="false">What did pirates eat?</button></h2>
<div hidden>

You can even hook into ARIA attributes for styling purpose!

button {
	background-color: #f7f7f7;
	border: #808080;
	border-radius: 0.25em;
	color: #272727;
	display: inline-block;
	padding: 0.5em 0.6875em;

[aria-pressed] {
	background-color: red;
	border-color: red;
	color: #ffffff;

The W3C maintains a collection of common patterns, with details about expectations around ARIA, roles, keyboard navigation, and so on.

It used to be difficult reading, but it’s gotten much better in recent years!

It’s the first place I look when building an interactive component.

This stuff is hard!

Accessibility is a speciality in-and-of itself. It’s often hard!

But knowing the basics and knowing where to look when you don’t know things is an important part of doing this professionally.

If you have a site or app that could benefit from a professional audit, get in touch. I’d love to work with you!

And if you want to learn more about accessibility so that you can spot this issues yourself, I strongly recommend my friend Sara Soueidan’s new course, Practical Accessibility Today.