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 WCAG criterion: 2.1.1 Keyboard (Level A) 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). Use native elements first. A If you absolutely must use a non-interactive element, add onclick="submitForm()" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();submitForm();}"> Submit And never remove the focus outline globally: *:focus { outline: none; } / Good — custom focus style that is still visible / *:focus-visible { outline: 3px solid #1a73e8; outline-offset: 2px; } The WCAG criterion: 2.4.2 Page Titled (Level A) Check the console.log(document.title || 'NO TITLE SET'); Each page should have a unique, descriptive title that follows the pattern Specific Page — Site Name: In a React app, update the title on navigation: function PricingPage() { useEffect(() => { document.title = 'Pricing Plans — AccessGuard'; }, []); return } For frameworks like Next.js or Astro, use the built-in The WCAG criterion: 3.1.1 Language of Page (Level A) View the page source and check the opening if (!lang) console.warn('Missing lang attribute on '); Add the correct BCP 47 language tag: If a section of the page is in a different language than the rest, use The French word for hello is bonjour. 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) 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); } }); For icon-only buttons, add For links that wrap only an image, ensure the image has alt text: Notice that the SVG icon in the button example gets 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) Load the page and press Tab once. If the first focusable element is not a "Skip to content" link, the mechanism is missing. Add a visually hidden link as the first element inside ... 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. Custom dropdown menus, modals, tabs, and accordions built from generic WCAG criterion: 4.1.2 Name, Role, Value (Level A) 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. A modal needs three things: focus trapping, Escape to close, and appropriate ARIA roles. aria-labelledby="modal-title" id="my-modal"> You will be billed monthly. Cancel any time. 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 Subscribe 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) Check whether the page uses semantic sectioning elements: landmarks.forEach(tag => { const count = document.querySelectorAll(tag).length; console.log( }); If any of these return zero, you have missing landmarks. Replace generic When you have multiple This simple structural change gives screen reader users a "table of contents" for your page that they can access at any time. Here is a quick-reference checklist you can use on any project: Work through these ten items in order. Issues one through six can typically be fixed in a single sitting. Issues seven through ten may require refactoring components, but every fix you ship makes the experience better for real users. Manual testing is essential — especially keyboard and screen reader testing — but automated scanners catch the low-hanging fruit instantly. Running a scanner after every deployment ensures that new code does not reintroduce resolved issues. AccessGuard's free accessibility scanner analyzes your pages against WCAG 2.1, flags every issue covered in this guide, and gives you prioritized, code-level fix suggestions so your team spends less time diagnosing and more time shipping. Try it today at accessguard.dev and fix accessibility issues on your website before your users — or their lawyers — find them first. for buttons and links, those elements are invisible to the keyboard.
How to Find It
How to Fix It
is keyboard-accessible and announces its role automatically. A
tabindex="0", a role, and keyboard event handlers:/ Bad — hides focus for everyone /
5. Missing Page Title
What It Is
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.How to Find It
of every page template. In a single-page application, verify that the title updates on route changes:// Quick audit in the console
How to Fix It
import { useEffect } from 'react';
or frontmatter title fields so this is handled automatically.6. Missing Language Attribute
What It Is
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.How to Find It
tag:const lang = document.documentElement.getAttribute('lang');
How to Fix It
lang on that element:7. Empty Links and Buttons
What It Is
How to Find It
document.querySelectorAll('a, button').forEach(el => {
How to Fix It
aria-label:
aria-hidden="true" to prevent the screen reader from trying to announce the SVG markup itself.8. Missing Skip Navigation
What It Is
How to Find It
How to Fix It
that becomes visible on focus:
.skip-link {
9. Inaccessible Custom Components
What It Is
How to Find It
How to Fix It — Accessible Modal Example
Confirm your subscription
function openModal() {
element, which handles focus trapping and the Escape key natively in modern browsers:
10. Missing ARIA Landmarks
What It Is
How to Find It
const landmarks = ['header', 'nav', 'main', 'footer', 'aside'];
<${tag}>: ${count} found);How to Fix It
or elements on one page, differentiate them with aria-label so screen reader users can tell them apart:
Putting It All Together: Your Fix Checklist
has an alt attribute (descriptive or empty)., aria-label, or aria-labelledby.. element has a valid lang attribute., , , , and appropriately.Automate What You Can
