How to Fix Accessibility Errors: Common WCAG Violations Solved
You've run an accessibility audit—maybe using axe, WAVE, or Lighthouse—and now you're staring at a list of errors. Some make sense immediately; others seem cryptic. Where do you start? What do these violations actually mean? How do you fix them without breaking something else?
This guide takes the most common accessibility errors and walks through practical fixes. No theory, no WCAG specification deep-dives—just solutions you can implement today.
Missing Alternative Text
This error appears more often than any other. When an image lacks an alt attribute, screen readers either skip it entirely or announce the filename, which rarely helps anyone understand the content.
The Fix
Every <img> element needs an alt attribute. What you put in that attribute depends on the image's purpose.
Informative images that convey content get descriptive text:
<!-- Before -->
<img src="quarterly-results-chart.png">
<!-- After -->
<img src="quarterly-results-chart.png"
alt="Bar chart showing Q4 revenue increased 23% compared to Q3">The description should convey the information the image communicates. For a chart, that's usually the key takeaway, not a literal description like "a bar chart with four bars."
Decorative images that don't add information get empty alt text:
<!-- Before -->
<img src="decorative-swirl.png">
<!-- After -->
<img src="decorative-swirl.png" alt="">Empty alt (not missing alt—there's a difference) tells screen readers to skip the image entirely. This is correct for decorative elements that would just add noise.
Functional images inside links or buttons get text describing the function:
<!-- Before -->
<a href="/search">
<img src="search-icon.svg">
</a>
<!-- After -->
<a href="/search">
<img src="search-icon.svg" alt="Search">
</a>Common Mistakes
Writing "image of" or "photo of" wastes words—screen readers already announce it's an image. Just describe the content directly.
Using the filename as alt text almost never helps. "IMG_2847.jpg" tells users nothing useful.
Empty Links and Buttons
When a link or button contains no text, screen readers announce "link" or "button" without any indication of purpose. Users have no idea where the link goes or what the button does.
The Fix for Links
Links need either visible text content or an accessible name via ARIA:
<!-- Before: Empty link, icon only -->
<a href="/cart" class="cart-icon">
<svg><!-- shopping cart icon --></svg>
</a>
<!-- After: Option 1 - Visually hidden text -->
<a href="/cart" class="cart-icon">
<span class="visually-hidden">Shopping cart</span>
<svg aria-hidden="true"><!-- shopping cart icon --></svg>
</a>
<!-- After: Option 2 - aria-label -->
<a href="/cart" class="cart-icon" aria-label="Shopping cart">
<svg aria-hidden="true"><!-- shopping cart icon --></svg>
</a>The visually hidden text approach keeps the accessible name in the HTML where it's visible to developers maintaining the code. The aria-label approach is more concise but can be easier to miss during updates.
The CSS for visually hidden text:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}The Fix for Buttons
Same principle—buttons need accessible names:
<!-- Before: Empty button -->
<button onclick="closeModal()">
<svg><!-- X icon --></svg>
</button>
<!-- After -->
<button onclick="closeModal()" aria-label="Close dialog">
<svg aria-hidden="true"><!-- X icon --></svg>
</button>Always mark decorative icons with aria-hidden="true" to prevent screen readers from attempting to announce them.
Missing Form Labels
Form fields without labels are nearly unusable for screen reader users. They hear "edit text" with no context about what information the field expects.
The Fix
Associate every input with a label using matching for and id attributes:
<!-- Before: No label -->
<input type="text" name="email" placeholder="Enter your email">
<!-- After: Proper label -->
<label for="email">Email Address</label>
<input type="email" id="email" name="email">If your design doesn't include visible labels (not recommended, but sometimes unavoidable), use aria-label:
<!-- Hidden label via ARIA -->
<input type="search" aria-label="Search products" placeholder="Search...">For fields where the label text exists elsewhere on the page, use aria-labelledby:
<!-- Table cell input labeled by column header -->
<input type="text" aria-labelledby="qty-header">Placeholder text is never an acceptable substitute for labels. It disappears when users type, has contrast issues, and isn't consistently supported by assistive technologies.
Insufficient Color Contrast
Low contrast text might look sleek to designers, but it's difficult to read for users with low vision, color blindness, or anyone viewing in bright sunlight or on a low-quality display.
WCAG Requirements
Normal text needs a 4.5:1 contrast ratio against its background. Large text (18pt/24px regular or 14pt/18.5px bold) needs 3:1. Non-text elements like icons and borders need 3:1.
The Fix
Adjust either the text color or background until you meet the threshold:
/* Before: Gray on white - 2.5:1 ratio (fails) */
.light-text {
color: #999999;
background: #ffffff;
}
/* After: Darker gray - 4.6:1 ratio (passes) */
.light-text {
color: #757575;
background: #ffffff;
}Use contrast checking tools during development. Browser DevTools show contrast ratios when you inspect colors. WebAIM's contrast checker lets you test arbitrary combinations.
If your brand colors don't meet contrast requirements, you have options: darken them slightly for text use, reserve them for large decorative elements, or use them only for non-essential visual treatments.
Missing Document Language
When the HTML document doesn't specify a language, screen readers may pronounce words using incorrect pronunciation rules. French text read with English pronunciation becomes incomprehensible.
The Fix
Add the lang attribute to the html element:
<!-- Before -->
<html>
<!-- After -->
<html lang="en">Use the appropriate ISO language code: en for English, es for Spanish, fr for French, de for German, and so on.
For content in a different language than the page default, add lang to that specific element:
<html lang="en">
<body>
<p>The French phrase <span lang="fr">c'est la vie</span> means "that's life."</p>
</body>
</html>Heading Hierarchy Issues
Screen reader users often navigate by headings—it's one of the most efficient ways to understand page structure. Skipped heading levels (like going from H1 to H3) or multiple H1 elements confuse this navigation.
The Fix
Use headings in sequential order without skipping levels:
<!-- Before: Skipped H2, multiple H1s -->
<h1>Our Company</h1>
<h1>Welcome</h1>
<h3>Services</h3>
<!-- After: Proper hierarchy -->
<h1>Our Company</h1>
<h2>Services</h2>
<h3>Web Development</h3>
<h3>Design</h3>
<h2>About Us</h2>Each page should have exactly one H1, typically matching or relating to the page title. Subsequent sections use H2, subsections use H3, and so on.
If you've been using heading tags for visual styling rather than structure, switch to CSS:
<!-- Before: H3 used for styling -->
<h3 class="sidebar-title">Recent Posts</h3>
<!-- After: Appropriate heading with CSS styling -->
<h2 class="sidebar-title">Recent Posts</h2>.sidebar-title {
font-size: 1rem; /* Style it however needed */
font-weight: 600;
}Missing Skip Links
Users who navigate by keyboard must tab through every navigation link to reach main content. On sites with extensive navigation, this means dozens of keystrokes on every page.
The Fix
Add a skip link at the very beginning of the page body:
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header><!-- Navigation --></header>
<main id="main-content"><!-- Content --></main>
</body>Style it to appear only when focused:
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px 16px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}This hides the link from sighted mouse users while making it the first thing keyboard users encounter.
Focus Not Visible
When focus indicators are removed or too subtle, keyboard users can't see where they are on the page. They're navigating blind.
The Fix
Never remove focus outlines without providing an alternative:
/* WRONG: Removes all focus indication */
*:focus {
outline: none;
}
/* RIGHT: Custom focus style */
*:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}The :focus-visible pseudo-class applies focus styles only for keyboard navigation, not mouse clicks—the best of both worlds.
Make sure your focus indicator is visible against all backgrounds:
/* Default focus */
:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Focus on dark backgrounds */
.dark-section :focus-visible {
outline-color: #ffffff;
}Keyboard Traps
When users can navigate into a component but can't navigate out using keyboard, they're trapped. This commonly happens with modals, embedded content, or poorly implemented widgets.
The Fix
Ensure every interactive element can be reached and escaped via keyboard:
// Modal focus management
function openModal() {
// Store current focus
previousFocus = document.activeElement;
// Show modal and move focus inside
modal.hidden = false;
modal.querySelector('button, [href], input').focus();
}
function closeModal() {
// Hide modal
modal.hidden = true;
// Return focus to trigger
previousFocus.focus();
}
// Close on Escape key
modal.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeModal();
}
});For embedded content like iframes that trap focus, ensure there's always a way out—typically via an Escape key handler.
Invalid ARIA Usage
ARIA can enhance accessibility, but incorrect ARIA often makes things worse. Common mistakes include using ARIA roles that conflict with native semantics, referencing IDs that don't exist, or using ARIA attributes on elements that don't support them.
The Fix
Wrong role on native element:
<!-- Before: Conflicting semantics -->
<button role="link">Click here</button>
<!-- After: Use the right element -->
<a href="/somewhere">Click here</a>Missing referenced element:
<!-- Before: aria-describedby references non-existent ID -->
<input aria-describedby="name-hint">
<!-- After: Ensure the referenced element exists -->
<input aria-describedby="name-hint">
<p id="name-hint">Enter your full legal name</p>Invalid ARIA on non-interactive element:
<!-- Before: aria-pressed on non-interactive element -->
<div aria-pressed="true">Toggle</div>
<!-- After: Use appropriate element with ARIA -->
<button aria-pressed="true">Toggle</button>The first rule of ARIA: if you can use native HTML to achieve the behavior, do that instead. A <button> is always better than a <div role="button">.
Taking Action Systematically
When faced with a long error list, prioritize by impact:
Fix first: Errors that completely block functionality—keyboard traps, missing labels, empty links Fix second: Errors affecting comprehension—missing alt text, heading issues, low contrast Fix third: Technical violations that may not immediately impact users—ARIA issues, language declaration
Work through your site systematically, testing as you go. Automated tools catch about 30-40% of issues, so combine automated scanning with manual keyboard and screen reader testing.
TestParty provides continuous monitoring that catches these issues as they're introduced, preventing accessibility debt from accumulating.
Schedule a TestParty demo and get a 14-day compliance implementation plan.
Related Resources
Stay informed
Accessibility insights delivered
straight to your inbox.


Automate the software work for accessibility compliance, end-to-end.
Empowering businesses with seamless digital accessibility solutions—simple, inclusive, effective.
Book a Demo