10 Top accessibility errors and how to avoid and fix them
Tags: webdev, a11y, tutorial
Published: 2024-04-15 20:55:15

In late December of 2023, [TPGi posted its list of the top accessibility errors](https://www.tpgi.com/the-top-accessibility-errors-found-in-2023/) found through its ARC automation tools in 2023. I am not just going to list them here as that will not be very useful to anyone. What I decided is that the only way there will be a chance that these do not end up on the list again at the end of 2024 is to explain these errors and offer a solution.

So here we go. There are ten errors in total, but some are closely related and will be grouped in this post.

## No link text, missing link alt attribute, or no label for button

Links are the lifeblood of the web. It is how we link pages together, it is how crawlers index the web, and it is how we get around websites and applications.

When link text is missing or is not meaningful, it becomes problematic for keyboard users and those using assistive technologies such as screen readers.

```html
<a href="mailto:peter@example.com"
  ><svg
    xmlns="http://www.w3.org/2000/svg"
    width="16"
    height="16"
    fill="currentColor"
    viewBox="0 0 16 16"
  >
    <path
      d="M15.964.686a.5.5 0 0 0-.65-.65L.767 5.855H.766l-.452.18a.5.5 0 0 0-.082.887l.41.26.001.002 4.995 3.178 3.178 4.995.002.002.26.41a.5.5 0 0 0 .886-.083zm-1.833 1.89L6.637 10.07l-.215-.338a.5.5 0 0 0-.154-.154l-.338-.215 7.494-7.494 1.178-.471z"
    /></svg
></a>
```

You might have seen code like this. We use an anchor link with a `mailto` and then instead of text, we add an SVG icon. If you look at this visually, it is a rather well-known icon of a paper plane and without any styling, the SVG icon will be blue, which most people will recognize as a link. In addition, you can tab to it as it is an interactive element however, if you do so with a screen reader, the screen reader will read out something like, "link".

> **Note:** You can get into the same problem when using a `button` element and the solutions are the same. To not make this post longer than it already is, I will not dig into the details, but feel free to ask me for more information in the comments.

{% youtube F4Pg_f84FXQ %}

The screen reader user therefore has no idea what this link will do if they trigger it. With the SVG nested in the HTML, we can use the `title` SVG element to fix the problem.

```html
<svg
  xmlns="http://www.w3.org/2000/svg"
  width="16"
  height="16"
  fill="currentColor"
  viewBox="0 0 16 16"
  role="img"
>
  <title>Email me</title>
  <path
    d="M15.964.686a.5.5 0 0 0-.65-.65L.767 5.855H.766l-.452.18a.5.5 0 0 0-.082.887l.41.26.001.002 4.995 3.178 3.178 4.995.002.002.26.41a.5.5 0 0 0 .886-.083zm-1.833 1.89L6.637 10.07l-.215-.338a.5.5 0 0 0-.154-.154l-.338-.215 7.494-7.494 1.178-.471z"
  />
</svg>
```

> **Note:** You may have noticed that I added a `role` of `img` to the SVG. This is so that the browser will not attempt to read the contents of the SVG but treat it as an image.

{% youtube 7b0xpSbNxIk %}

What if the SVG is not inlined in the HTML but added as a background image using CSS? You have two options with the same result. The first is to add a class called `visually-hidden` to your global CSS

```css
.visually-hidden {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}
```

You would then use the following HTML:

```html
<a href="mailto:peter@example.com" class="icon icon-email"><span class="visually-hidden">Email me</span></a>
```

The CSS will hide the text visually, but the screen reader will still read it and use it as the accessible text of the link. Another option is to use `aria-label`.

```html
<a aria-label="Email me" href="mailto:peter@example.com" class="icon icon-email"</a>
```

{% youtube ba1333sfWTI %}

Great, but did I mean by links having meaningful text? Consider the following:

```html
Learn more about writing meaningful link text. <a href="https://wcag.com/blog/writing-meaningful-link-text/">Click here</a>
```

If you look at this visually in the browser, you might not see the problem. However, for someone using a screen reader and either tabbing through the links on the page or using the links menu shown earlier, all they will hear is "Click here". This is what is meant by having meaningful text.

It is very easy to fix though.

```html
<a href="https://wcag.com/blog/writing-meaningful-link-text/">Learn more about writing meaningful link text.</a>
```

The video below walks through this as well.

{% youtube c-vyPmUov6c %}

## Positive `tab-index` and non-interactive elements in the tab order

As mentioned, users who rely on a keyboard to navigate your site, power users, and those using assistive technologies such as screen readers very often use their tab key to move through your sites and applications.

By default, the only elements that will receive focus are interactive elements such as links, buttons, and form elements. There are ways to force a non-interactive element to be added to the tab order or control the tab order, and there is also a way to make an element focusable without adding it to the tab order. This is commonly done when the focus state is managed via JavaScript. I will not touch on this last one here.

You can control the natural tab order by using positive numbers with the `tab-index` attribute. There is never a good reason for this. I very rarely use blanket statements like this, but I believe this is one of the instances where it is warranted. This practice is extremely brittle and can quickly break the user experience and user expectations. Instead, use semantic HTML and ensure that the HTML source order matches your desired tab order.

> Remember you can also change the layout using CSS, but do not try to control the tab order, let the browser do the work for you.

Another way you can use `tab-index` is with a value of 0 (zero). This allows you to add a non-interactive element into the "natural" tab order. I wrapped natural in quotes in the previous sentence because while the item will be added in the natural tab order, there is nothing natural about a non-interactive element being made focusable.

This is another one where I would advise relying on sensible HTML source order and allowing the browser to take over from there.

## Missing `alt` text

It is hard to believe that at the end of 2023 and therefore in 2024 this is still one of the top errors showing up in accessibility audits.

The guidelines are clear, and adding the appropriate content for an `alt` attribute is quick and easy. This leads me to believe that most of these are introduced not through user error but because of the lack of support from various tooling.

I hate to call out a company, but I stopped using Buffer because their tools do not allow me to specify alternative text when adding an image to a scheduled social media post. Now, does the blame lie on the side of Buffer or on the side of the social media platforms that do not provide an API to specify alternative text? I do not know, but I would love to know. If you know, let me know in the comments.

Whenever you can, please take a moment to provide alternative text for any media that conveys meaning and is not purely decorative. While there is no limit on the number of characters you can use in an `alt` attribute, try to be descriptive but concise. Tools such as the various chatbots can also help you here if, for example, your first language is not English and you want to provide alternative text in English.

As an example let's say the hero image for this post was the only place where the title was conveyed to the reader. In that case, I would markup the image as follows:

```html
<img alt="An image depicting people of various ages and abilities. Overlayed is the text, '10 Top accessibility errors and how to avoid and fix them'" src="/media/hero.webp" width="1200" height="400" />
```

Seeing that the image is purely decorative and the title is available to screen readers as text, I would use an empty value for the `alt` attribute:

```html
<img alt="" src="/media/hero.webp" width="1200" height="400" />
```

## Lists not nested correctly

The primary concern is that a developer does not use the most appropriate list type for the content. Let's say you are writing the instructions to make a pizza. Before making the pizza you would need to go to the store and buy the ingredients. Once you have the ingredients you need to follow the needed steps to ensure a successful and delicious pizza. However, the order you buy the ingredients is not important only that you buy them all.

Now you could, and probably would, mark this up as two separate lists, but for the sake of the example let's use a primary list and one nested list.

You could use an unordered list and nest another unordered list, but then you would have made the mistake that this error is all about. Instead, our primary list is an ordered list and our ingredient list is a nested unordered list.

```html
<ol>
  <li>Head to the store and buy the following ingredients:
    <ul>
      <li>Bread flour</li>
      <li>Dry yeast</li>
      <li>Extra virgin Greek olive oil</li>
      <li>Tomato sauce</li>
      <li>Cheese</li>
      <li>Fresh basil</li>
    <ul>
  </li>
  <li>Proof the yeast</li>
  <li>Knead the dough</li>
  <li>Let the dough rise</li>
  <li>Preheat the pizza stone</li>
  <li>Prepare the toppings</li>
  <li>Flatten and stretch the dough</li>
  <li>Brush the dough with olive oil</li>
  <li>Spread with tomato sauce and sprinkle with toppings</li>
  <li>Slide pizza into the oven</li>
  <li>Bake</li>
  <li>Stuff thy face 🍕</li>
</ol>
```

## Duplicate labels used

The labels here refer to the labels used with form elements. While it is critical to ensure your labels are unique and descriptive, I have found that it is still very often the case that people either do not specify labels (relying on the `placeholder`) or specify a label, but do not associate the label with the `input` element.

```html
<label>Username</label>
<input type="text" name="username" id="username" />

<label>Email address</label>
<input type="email" name="email" id="email" />
```

{% youtube rPzNB9yr_sk %}

**Please, please** ensure that you _always_ provide unique, clear label text, associated with the relevant input. What do I mean by associating a label with an input? This is achieved by a combination of the `for` attribute on the `label` element and an associated `id` attribute on the form element.

```html
<label for="username">Username</label>
<input type="text" name="username" id="username" />

<label for="email">Email address</label>
<input type="email" name="email" id="email" />
```

{% youtube 4BglwbSP-9Q %}

## Invalid `aria-labelledby` and `aria-describedby`

These errors are caused by the same underlying problem and are solved by the same solution so I will describe them together. Both of these attributes work in much the same way as we just discussed with associating a `label` element and form element.

Instead of the `for` attribute, however, you use either `aria-labelledby` or `aria-describedby`. The value of these attributes must match the value of an `id` attribute on another element. For example:

```html
<section aria-labelledby="my-portfolio-section-title">
  <h2 id="my-portfolio-section-title">My portfolio</h2>
</section>
```

> **NOTE:** A `section` element without an accessible name has an implicit `generic` role and therefore has no real semantic meaning. The example above does have an accessible name thanks to the `aria-labelledby` attribute. In this case, the section has a role of `region` and as such will show up in the landmark roles section of screen reader tools such as VoiceOver on macOS.

An instance where using the technique above is incredibly useful is with the `nav` element. On a documentation site, for example, you might have a header with your primary navigation, a sidebar with navigation for your documentation, a table of contents for the current page, and a secondary navigation in the footer. If all of these are marked up using the `nav` element, you will have four landmarks, but they will all be identified as navigation without any distinguishing label. You can fix this with either an `aria-label` or `aria-labelledby`.

{% youtube 1Lo5psclMkc %}

```html
<nav aria-label="Primary">
...
</nav>

<aside>
  <nav aria-labelledby="sidebar-title-get-started">
    <h3 id="sidebar-title-get-started">Get started</h3>
    ...
  </nav>

  <nav aria-labelledby="sidebar-title-learn-astro">
    <h3 id="sidebar-title-learn-astro">Learn Astro</h3>
    ...
  </nav>
</aside>
```

{% youtube 4DULO2DLnf0 %}

### Conclusion

I hope you found all of this helpful and that it will help you avoid these very common errors and allow you to fix them when you do come across them. Now go and make the web awesome and accessible for all.