5 Common Web App Accessibility Issues And How To Fix Them

 |  Web Accessibility, HTML

Over the years, I’ve worked on many large web app projects with developers of varying levels of experience and often see the same web accessibility issues crop up. In this article, I detail the 5 most common web accessibility issues I’ve come across and offer solutions for them.

Are you making / have you made these mistakes in your projects?

1) Using An Input Without An Associated Label

Wrong - Using plain text next to the input

Username <input type="text" name="username">

The text “Username” is not associated with the <input> so when a screen reader announces the <input>, it won’t announce that it’s for entering a username. The <input> will be announced along the lines of “input, edit text” so a screen reader user would have no idea what this <input> is for or what to enter.

The text “Username” also won’t act as a click / touch target to set focus to the <input>.

Wrong - Using a label but not linking it to the input

<input type="text" name="username">

When using a <label>, it must be associated to it’s corresponding <input>. This can be done either with the for attribute or by wrapping the <label> around the <input>. As neither of these approaches has been used here, the <label> is not associated to the <input>.

Also note that a <label> has a specific semantic meaning which is to provide a label for a corresponding <input>. A <label> must therefore not be used for any general text content that is not acting as a label for an <input>. I have seen <label> elements used just to visually make some general text bold which is wrong.

Wrong - Using a label with for attribute but the input id is missing / wrong

<label for="firstName">First name</label>  
<input type="text" name="firstName">

<label for="lastName">Last name</label>
<input type="text" name="name" id="name">

With the “First name”, the <input> doesn’t contain the id attribute and with the “Last name”, the input contains the id attribute but with the value name and not lastName. In both cases, the <label> is not correctly associated with it’s <input>.

Also note that attribute values are case-sensitive so a for="Username" attribute will not match an <input> with the attribute id="username".

Correct - Using a label and input with correct for & id attribute values

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

Correct - Using a label wrapped around the input

<input type="text" name="username">

Correct - Using the aria-labelledby attribute

<h2 id="resetHeading">Reset Password</h2>

<span id="resetUsername">Username</span>
<input type="text" name="username" aria-labelledby="resetHeading resetUsername">

If there are one or more existing elements that contain text suitable to act as a label for an <input>, the IDs of these elements can be referenced using the aria-labelledby attribute. In this example, the <input> will be announced along the lines of “reset password username, edit text”.

Providing the extra context of “reset password” may be extremely useful in some scenarios for example on a sign up page that consists a “Username” input in a section for creating a new account and a “Username” input in another section for resetting a forgotten password.

Correct - Using the aria-label attribute for a non-visual label

<input type="text" name="searchTerm" aria-label="Search">
<button type="submit">Search</button>

In some very specific cases, a visual label is not required for an <input> as the purpose of the <input> is clear from the surrounding content. In this example, for a fully sighted user, it’s clear what purpose the <input> serves due the “Search” button next to it. For screen reader users though, this visual connection cannot be made. The aria-label attribute can be used to provide a non-visual label for the search <input>.

Further reading on labels for user input

WCAG 2.1 (Level A) - 3.3.2 Labels or Instructions https://www.w3.org/WAI/WCAG21/quickref/#labels-or-instructions

2) Using An Icon Font Without Text Alternative

Icon fonts provide a convenient way to add scalable, styleable icons to a website (although now arguably SVG icons provide a better approach). The markup to display an icon is typically a <i> or <span> tag with a class applied to it which includes a :before or :after selector to output a specific Unicode character. This Unicode character happens to visually display an icon in the font file but a screen reader either won’t announce this character or will announce a literal description of the icon, neither of which is ideal.

With this in mind, let’s have a look at some common web accessibility issues when using an icon font.

Wrong - Icon with no text alternative

<i class="fas fa-download"></i>

Wrong - Hiding text on small viewports

.download-text {
display: none;

@media screen and (min-width: 768px) {
.download-text {
display: inline;

<span class="download-text">Download</span>
<i class="fas fa-download"></i>

Correct - Icon with text alternative

<i class="fas fa-download" aria-hidden="true"></i>

Note that the attribute aria-hidden="true" has been applied to the <i> to ensure the screen reader doesn’t announce the Unicode character.

Correct - Having text alternative still available when displayed text is hidden on small viewports

.download-text {
display: none;

@media screen and (min-width: 768px) {
.download-text {
display: inline;

<span class="sr-only">Download</span>
<span class="download-text" aria-hidden="true">Download</span>
<i class="fas fa-download" aria-hidden="true"></i>

The class sr-only (screen reader only) has been applied to the first <span> which positions the text outside of the viewport so it can’t visually be seen but still exists in the DOM and is announced by a screen reader.

Both the text that is only displayed on larger viewports and the icon <i> tag have the attribute aria-hidden="true" so they are not announced by a screenreader.

Further reading on icon font accessibility


3) Adding A Click Handler To A Non-Interactive Element

Wrong - Adding click handlers to a div and span in React and Vue

<div onClick={this.addToBasket}>Add To Basket</div>  

<span @click="checkout">Checkout</span>

Unlike a native <button> or <a>, a keyboard user can’t interact with and invoke click handlers on <div> or <span> elements and a screen reader user isn’t made aware that the <div> or <span> elements can be interacted with.

Correct - Use click handlers with interactive elements

<button onClick={this.addToBasket}>Add To Basket</button>  

<a href="#" @click="checkout">Checkout</a>

Further reading on accessible JavaScript click handlers


4) Not Using A Live Region For Important Information

With Progressive Web Apps, Single Page Applications and client-side rich webpages, the content on a webpage can change dynamically without the need for a full page reload. When content in part of a page is dynamically changed, a fully-sighted user can visually see these changes but a screen reader user is not aware of the changes.

Live regions provide a way to have dynamic content changes announced by screen readers.

Wrong - Important information will not be announced

<div>Your message has been sent</div>

<span>Unable to place order as you're offline<span>

Correct - live-region used to announce important information

<div aria-live="polite" role="status">
Your message has been sent

<div aria-live="assertive">
Unable to place order as you're offline

Further reading on live regions


5) Not Providing The href Attribute On An Anchor Tag

An anchor element should have a href attribute with a value to ensure the element can receive focus by a keyboard user and to ensure the link is announced correctly by screen readers.

Wrong - No href attribute

<a onClick={this.upload}>Upload</a>  

Wrong - Empty href attribute

<a href="" onClick={this.upload}>Upload</a>  

Correct - A href attribute with a value is provided

<a href="#" onClick={this.upload}>Upload</a>