Back to Blog
·13 min read·AccessGuard Team

How to Fix Accessibility Issues on Your Website: A Practical Guide to the Top 10 Problems

# How to Fix Accessibility Issues on Your Website: A Practical Guide to the Top 10 Problems

If you need to fix accessibility issues on your website, you are not alone. Over 96% of home pages have detectable WCAG failures, according to the WebAIM Million annual analysis. The good news is that most of these problems fall into a handful of categories, and every single one of them is fixable with straightforward HTML, CSS, and ARIA changes.

This guide walks you through the ten most common accessibility issues, explains why each one matters, shows you how to detect it, and gives you copy-paste code to resolve it. By the end you will have a concrete checklist you can apply to any site today.

Why Fixing Accessibility Issues Matters

Before diving into the fixes, it helps to understand the stakes. Accessibility failures affect real people: screen reader users who cannot parse your navigation, keyboard-only users who get trapped inside a modal, and users with low vision who cannot read light-gray text on a white background. Beyond the ethical imperative, inaccessible sites face legal risk under the ADA and the European Accessibility Act, and they lose search ranking signals that Google ties to Core Web Vitals and semantic HTML.

Every fix below maps to one or more WCAG 2.1 success criteria. Where relevant, the criterion is noted so you can cross-reference your own audit.

1. Missing Alt Text on Images

What It Is

When an element lacks an alt attribute, assistive technologies have no way to convey the image's purpose. Some screen readers will read the file name instead, producing gibberish like "DSC underscore zero four three seven dot jpeg."

WCAG criterion: 1.1.1 Non-text Content (Level A)

How to Find It

Open your browser DevTools, go to the Console, and run:

document.querySelectorAll('img:not([alt])').forEach(img => {

console.warn('Missing alt:', img.src);

});

Alternatively, any automated scanner will flag this immediately.

How to Fix It

For informative images, write alt text that communicates the same information the image conveys:


alt="Bar chart showing Q4 revenue increased 18% compared to Q3">

For decorative images that add no information, use an empty alt attribute so screen readers skip them entirely:


Never omit the alt attribute altogether. An empty string and a missing attribute are treated very differently by assistive technology.

2. Low Color Contrast

What It Is

Text that does not have enough contrast against its background is difficult or impossible to read for users with low vision or color vision deficiencies. WCAG requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (18px bold or 24px regular and above).

WCAG criterion: 1.4.3 Contrast (Minimum) (Level AA)

How to Find It

Use the Chrome DevTools color picker: inspect any text element, click the color swatch in the Styles panel, and the picker will display the contrast ratio along with a pass/fail indicator. For a full-page scan, browser extensions like the WAVE toolbar highlight every low-contrast instance.

How to Fix It

Adjust your foreground or background color until the ratio meets the threshold:

/ Bad — ratio approximately 2.5:1 /

.subtle-text {

color: #999999;

background-color: #ffffff;

}

/ Good — ratio approximately 7:1 /

.subtle-text {

color: #595959;

background-color: #ffffff;

}

If your brand palette includes colors that fail contrast checks, create an accessible variant for text use while keeping the original for large decorative elements where the 3:1 threshold applies.

A useful tip: define contrast-safe CSS custom properties so that every component references them:

:root {

--color-text-primary: #1a1a1a; / 15.4:1 on white /

--color-text-secondary: #595959; / 7:1 on white /

}

3. Missing Form Labels

What It Is

A form input without a programmatically associated label leaves screen reader users guessing what information to enter. Placeholder text is not a substitute because it disappears on focus and is not reliably announced.

WCAG criterion: 1.3.1 Info and Relationships (Level A), 4.1.2 Name, Role, Value (Level A)

How to Find It

Run the following snippet to catch orphaned inputs:

document.querySelectorAll('input, select, textarea').forEach(el => {

const id = el.id;

const hasLabel = id && document.querySelector(label[for="${id}"]);

const isWrapped = el.closest('label');

const hasAriaLabel = el.getAttribute('aria-label');

const hasAriaLabelledby = el.getAttribute('aria-labelledby');

if (!hasLabel && !isWrapped && !hasAriaLabel && !hasAriaLabelledby) {

console.warn('No label for:', el);

}

});

How to Fix It

The most robust approach is an explicit with a matching for/id pair:


When a visible label is not part of the design (for example, a single search field with only a button), use aria-label:


4. No Keyboard Navigation

What It Is

Many users cannot use a mouse. They rely on the Tab key to move between interactive elements and Enter or Space to activate them. When sites use non-interactive elements like

or for buttons and links, those elements are invisible to the keyboard.

WCAG criterion: 2.1.1 Keyboard (Level A)

How to Find It

Put your mouse aside and try to navigate your entire site using only the keyboard. Every interactive element should be reachable with Tab, and you should see a visible focus indicator at all times. If you get stuck inside a component and cannot Tab out, you have a keyboard trap (2.1.2).

How to Fix It

Use native elements first. A

If you absolutely must use a non-interactive element, add tabindex="0", a role, and keyboard event handlers:

tabindex="0"

onclick="submitForm()"

onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();submitForm();}">

Submit

And never remove the focus outline globally:

/ Bad — hides focus for everyone /

*:focus {

outline: none;

}

/ Good — custom focus style that is still visible /

*:focus-visible {

outline: 3px solid #1a73e8;

outline-offset: 2px;

}

5. Missing Page Title

What It Is

The </code> element is the first thing a screen reader announces when a page loads. It also appears in browser tabs and search engine results. A missing or generic title like "Untitled" or "Home" on every page makes it impossible for users to distinguish between open tabs.</p> <p class="text-gray-700 leading-relaxed my-4"><strong>WCAG criterion:</strong> 2.4.2 Page Titled (Level A)</p> <h3 class="text-xl font-bold mt-8 mb-3">How to Find It</h3> <p class="text-gray-700 leading-relaxed my-4">Check the <code class="bg-gray-100 text-red-700 px-1.5 py-0.5 rounded text-sm"><head></code> of every page template. In a single-page application, verify that the title updates on route changes:</p> <pre class="bg-gray-900 text-gray-100 rounded-xl p-4 overflow-x-auto my-6"><code>// Quick audit in the console <p class="text-gray-700 leading-relaxed my-4">console.log(document.title || 'NO TITLE SET');</p> <p class="text-gray-700 leading-relaxed my-4"></code></pre></p> <h3 class="text-xl font-bold mt-8 mb-3">How to Fix It</h3> <p class="text-gray-700 leading-relaxed my-4">Each page should have a unique, descriptive title that follows the pattern <strong>Specific Page — Site Name</strong>:</p> <pre class="bg-gray-900 text-gray-100 rounded-xl p-4 overflow-x-auto my-6"><code><head> <p class="text-gray-700 leading-relaxed my-4"> <title>Pricing Plans — AccessGuard

In a React app, update the title on navigation:

import { useEffect } from 'react';

function PricingPage() {

useEffect(() => {

document.title = 'Pricing Plans — AccessGuard';

}, []);

return

...
;

}

For frameworks like Next.js or Astro, use the built-in or frontmatter title fields so this is handled automatically.

6. Missing Language Attribute

What It Is

The lang attribute on the element tells screen readers which language rules to use for pronunciation. Without it, a screen reader set to English might attempt to read French content with English phonetics, producing unintelligible audio.

WCAG criterion: 3.1.1 Language of Page (Level A)

How to Find It

View the page source and check the opening tag:

const lang = document.documentElement.getAttribute('lang');

if (!lang) console.warn('Missing lang attribute on ');

How to Fix It

Add the correct BCP 47 language tag:




If a section of the page is in a different language than the rest, use lang on that element:

The French word for hello is bonjour.

7. Empty Links and Buttons

What It Is

A link or button that contains no text and no accessible name is announced by a screen reader as simply "link" or "button," giving the user zero context. This is extremely common with icon-only buttons that rely solely on a visual icon.

WCAG criterion: 4.1.2 Name, Role, Value (Level A), 2.4.4 Link Purpose (Level A)

How to Find It

document.querySelectorAll('a, button').forEach(el => {

const text = el.textContent.trim();

const ariaLabel = el.getAttribute('aria-label');

const ariaLabelledby = el.getAttribute('aria-labelledby');

const title = el.getAttribute('title');

const imgAlt = el.querySelector('img[alt]')?.alt;

if (!text && !ariaLabel && !ariaLabelledby && !title && !imgAlt) {

console.warn('Empty interactive element:', el);

}

});

How to Fix It

For icon-only buttons, add aria-label:


For links that wrap only an image, ensure the image has alt text:


AccessGuard home page

Notice that the SVG icon in the button example gets aria-hidden="true" to prevent the screen reader from trying to announce the SVG markup itself.

8. Missing Skip Navigation

What It Is

Keyboard users and screen reader users must Tab through every navigation link on every page load before reaching the main content. A skip navigation link lets them jump straight past the header.

WCAG criterion: 2.4.1 Bypass Blocks (Level A)

How to Find It

Load the page and press Tab once. If the first focusable element is not a "Skip to content" link, the mechanism is missing.

How to Fix It

Add a visually hidden link as the first element inside that becomes visible on focus:


...

...

.skip-link {

position: absolute;

top: -100%;

left: 0;

padding: 0.75rem 1.5rem;

background: #1a1a1a;

color: #ffffff;

font-size: 1rem;

z-index: 1000;

transition: top 0.2s ease;

}

.skip-link:focus {

top: 0;

}

This link is invisible during normal browsing but slides into view the moment a keyboard user presses Tab, giving them a one-key shortcut to the main content.

9. Inaccessible Custom Components

What It Is

Custom dropdown menus, modals, tabs, and accordions built from generic

elements often lack the keyboard interactions and ARIA attributes that native HTML elements provide for free. A custom select menu that only opens on mouse click is completely unusable for keyboard and screen reader users.

WCAG criterion: 4.1.2 Name, Role, Value (Level A)

How to Find It

Attempt to use every custom interactive component with only the keyboard. Then turn on a screen reader (VoiceOver on macOS, NVDA on Windows) and verify that the role, state, and value of each component are announced correctly.

How to Fix It — Accessible Modal Example

A modal needs three things: focus trapping, Escape to close, and appropriate ARIA roles.

function openModal() {

const modal = document.getElementById('my-modal');

modal.removeAttribute('hidden');

// Move focus into the modal

modal.querySelector('#modal-confirm').focus();

// Trap focus inside the modal

modal.addEventListener('keydown', trapFocus);

}

function trapFocus(event) {

const modal = document.getElementById('my-modal');

const focusable = modal.querySelectorAll(

'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'

);

const first = focusable[0];

const last = focusable[focusable.length - 1];

if (event.key === 'Escape') {

closeModal();

return;

}

if (event.key === 'Tab') {

if (event.shiftKey && document.activeElement === first) {

event.preventDefault();

last.focus();

} else if (!event.shiftKey && document.activeElement === last) {

event.preventDefault();

first.focus();

}

}

}

function closeModal() {

const modal = document.getElementById('my-modal');

modal.setAttribute('hidden', '');

modal.removeEventListener('keydown', trapFocus);

// Return focus to the element that opened the modal

document.getElementById('open-modal-btn').focus();

}

Wherever possible, consider using the native

element, which handles focus trapping and the Escape key natively in modern browsers:


Confirm your subscription

You will be billed monthly. Cancel any time.

10. Missing ARIA Landmarks

What It Is

ARIA landmark roles — or their equivalent HTML5 semantic elements — let screen reader users jump between major sections of the page. Without them, navigating a page is like reading a book with no chapter headings.

WCAG criterion: 1.3.1 Info and Relationships (Level A)

How to Find It

Check whether the page uses semantic sectioning elements:

const landmarks = ['header', 'nav', 'main', 'footer', 'aside'];

landmarks.forEach(tag => {

const count = document.querySelectorAll(tag).length;

console.log(<${tag}>: ${count} found);

});

If any of these return zero, you have missing landmarks.

How to Fix It

Replace generic

wrappers with semantic elements:


...

...

...

...

When you have multiple