Back to Blog
Top 10 EAA Violations We Find on European Websites — and How to Fix Them in an Afternoon
Ten WCAG 2.1 AA failures that show up on most EU sites, with copy-paste fixes. Fix these and you address most automated-detectable EAA findings.
Based on the WebAIM Million 2024 Annual Accessibility Analysis of the top 1 million homepages. These six failure types account for over 96% of all detected WCAG errors. The violations below map directly to these categories.
How we ranked them
Order is by prevalence × legal risk, not raw frequency. A violation that is everywhere but rarely cited (e.g. minor heading skips) ranks lower than something less common but routinely flagged in complaints (e.g. unlabelled form fields on a checkout page).
According to the WebAIM Million 2024 report, these six failure types alone appear on over 96% of all pages with detected errors — giving them the highest signal-to-noise ratio in both audits and enforcement actions.
For each, you get:
- The WCAG 2.1 AA criterion it fails
- The axe-core rule that detects it
- Why it gets you a complaint
- The fix, with code
Let's go.
1. Missing alt text on meaningful images
WCAG: 1.1.1 Non-text Content (Level A) · axe rule: image-alt
This is the single most-cited finding in WCAG audits and the easiest to fix.
The mistake almost everyone makes: leaving alt off entirely on a decorative image, then leaving it off on a meaningful image too. Screen readers then announce nothing for the meaningful image — and the user has no idea there's a product photo, a chart, or a CAPTCHA.
<!-- ❌ Bad: meaningful image with no alt -->
<img src="/products/red-chair.jpg">
<!-- ✅ Good: meaningful image, descriptive alt -->
<img src="/products/red-chair.jpg" alt="Red leather armchair, mid-century style, side view">
<!-- ✅ Good: decorative image, empty alt is correct -->
<img src="/decorative/swoosh.png" alt="">
Important nuance: alt="" is the right answer for genuinely decorative images. It is not the right answer for "I don't know what to write." A missing description on a product image will cost you a complaint; an empty alt on a decoration is the spec-correct choice.
2. Insufficient text-to-background contrast
WCAG: 1.4.3 Contrast (Minimum) (Level AA) · axe rule: color-contrast
You need a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (≥18pt or ≥14pt bold). This is the most common AA failure on landing pages — light grey text on white backgrounds, brand-coloured buttons that do not pass against the background.
How to check fast: paste your foreground/background hex into WebAIM's contrast checker or your browser devtools (Inspect → Accessibility tab in Chrome/Firefox).
/* ❌ Bad: 2.6:1 ratio, fails AA */
.muted { color: #aaaaaa; background: #ffffff; }
/* ✅ Good: 4.6:1 ratio, passes AA */
.muted { color: #767676; background: #ffffff; }
/* ✅ Good for placeholders and disabled states (still readable) */
.disabled { color: #595959; background: #ffffff; } /* 7:1 */
Contrarian note: many design systems (Material, Apple HIG, Tailwind defaults) ship gray-300, gray-400 as common text colors against white. Both fail AA. Audit your design tokens once, not your pages.
3. Form inputs without programmatic labels
WCAG: 1.3.1 Info and Relationships (Level A), 4.1.2 Name, Role, Value (Level A) · axe rule: label
This is the highest-legal-risk violation on e-commerce sites. A checkout form where screen readers cannot announce "Email", "Postal code", "CVV" — that's the kind of failure that triggers French enforcement actions like the November 2025 lawsuits against Carrefour and Auchan (see France's enforcement data).
<!-- ❌ Bad: visual placeholder, no label -->
<input type="email" placeholder="Email address">
<!-- ❌ Bad: label not associated -->
<label>Email</label>
<input type="email">
<!-- ✅ Good: explicit label association -->
<label for="email">Email</label>
<input id="email" type="email">
<!-- ✅ Good: aria-label when visual label is impractical (e.g. icon-only) -->
<input type="search" aria-label="Search products">
<!-- ✅ Good: aria-labelledby pointing to existing visible text -->
<span id="zip-label">Postal code</span>
<input aria-labelledby="zip-label">
placeholder is never a substitute for a label. It disappears when the user starts typing.
4. Missing skip-to-content link
WCAG: 2.4.1 Bypass Blocks (Level A) · axe rule: skip-link
Keyboard-only and screen-reader users have to tab through every navigation link before reaching the main content — on every page. A skip link lets them jump past it.
<!-- Place this as the very first focusable element in <body> -->
<a href="#main" class="skip-link">Skip to main content</a>
<!-- ... -->
<main id="main">...</main>
/* Visually hide until focused — never use display:none */
.skip-link {
position: absolute;
left: -9999px;
top: 0;
}
.skip-link:focus {
left: 0;
background: #000;
color: #fff;
padding: 0.75rem 1rem;
z-index: 9999;
}
If you use Next.js / Nuxt / SvelteKit, this is one component imported into your root layout. Five minutes, fixes one of the most-flagged violations.
5. Empty links and buttons
WCAG: 2.4.4 Link Purpose (Level A), 4.1.2 Name, Role, Value (Level A) · axe rules: link-name, button-name
Icon-only buttons and links — the hamburger menu, the search icon, the close-X on modals — frequently ship without an accessible name. Screen readers announce "button" with no clue what it does.
<!-- ❌ Bad: icon button, no name -->
<button><svg>...</svg></button>
<!-- ✅ Good: aria-label provides the name -->
<button aria-label="Open navigation menu">
<svg aria-hidden="true">...</svg>
</button>
<!-- ✅ Good: visually-hidden text for screen readers -->
<button>
<svg aria-hidden="true">...</svg>
<span class="sr-only">Open navigation menu</span>
</button>
The aria-hidden="true" on the SVG matters — without it, the screen reader may announce both the SVG (often as "graphic") and the label, which is noisy.
6. Missing language attribute on <html>
WCAG: 3.1.1 Language of Page (Level A) · axe rule: html-has-lang
Without lang, screen readers do not know which pronunciation rules to use — French content gets read in English phonetics, German in French, and the result is unintelligible.
<!-- ❌ Bad -->
<html>
<!-- ✅ Good -->
<html lang="en">
<!-- ✅ Good for multilingual content with a primary language -->
<html lang="de">
<body>
<p>The German term <span lang="en">"deadline"</span> is widely used.</p>
</body>
</html>
This is one attribute. There is no excuse for missing it.
7. Broken heading hierarchy
WCAG: 1.3.1 Info and Relationships (Level A) · axe rule: heading-order
Screen reader users navigate by headings — "next heading" is one of the most-used commands. If your page has an h1, then jumps straight to h4, skipping h2 and h3, the hierarchy is broken and navigation fails.
<!-- ❌ Bad: h1 → h4 → h2 -->
<h1>Page title</h1>
<h4>Subsection</h4>
<h2>Another section</h2>
<!-- ✅ Good: monotonic, no skipped levels -->
<h1>Page title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
<h2>Next section</h2>
A common cause is using heading levels for visual styling. Don't. Use the correct level for structure, then style with CSS.
8. Interactive elements that aren't keyboard accessible
WCAG: 2.1.1 Keyboard (Level A) · axe rule: keyboard (manual + automated checks)
Every clickable element must be reachable and operable via keyboard alone. The most common failure: using <div onclick=...> instead of <button>.
<!-- ❌ Bad: not keyboard-focusable, not announced as button -->
<div class="btn" onclick="doThing()">Click me</div>
<!-- ✅ Good: native semantics give keyboard + screen reader for free -->
<button type="button" onclick="doThing()">Click me</button>
<!-- ✅ Good: if you absolutely must use a div, you have to add all of this -->
<div
role="button"
tabindex="0"
onclick="doThing()"
onkeydown="if(event.key==='Enter'||event.key===' '){doThing()}"
>
Click me
</div>
The third option is almost always wrong. Use a <button>. The semantic element wins on every metric — accessibility, keyboard, focus styles, screen reader announcement, browser autofill.
9. Modals without focus management
WCAG: 2.4.3 Focus Order (Level A), 2.1.2 No Keyboard Trap (Level A) · partial automation, mostly manual
A modal that does not trap focus is a frequent cause of "I opened the modal and then couldn't get out." When a modal opens, focus must move into the modal; while it's open, Tab should cycle within it; on close, focus returns to the trigger.
The right way in 2026 is the native <dialog> element:
<!-- ✅ Good: native dialog with showModal() handles focus trap automatically -->
<button type="button" onclick="document.getElementById('confirm-dialog').showModal()">
Delete account
</button>
<dialog id="confirm-dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm deletion</h2>
<p>This cannot be undone.</p>
<form method="dialog">
<button value="cancel">Cancel</button>
<button value="confirm">Delete</button>
</form>
</dialog>
For React/Vue/Svelte component libraries, verify your modal component supports aria-modal, focus trap, and ESC-to-close. The popular ones (Radix UI, Headless UI, Mantine Modal, MUI Dialog) all do — but custom modals or older third-party widgets often don't.
10. Cookie banners that block everything
WCAG: 2.1.1 Keyboard (Level A), 1.4.13 Content on Hover or Focus (Level AA), 4.1.2 · partial automation
This is the most user-visible failure on European sites because GDPR has forced cookie banners onto every site, and most banner implementations are accessibility disasters:
- Tab order traps the user inside the banner forever
- Buttons are unlabelled or labelled with vague text ("Accept all" without context)
- The banner overlays the main content but doesn't carry the appropriate role / aria-modal
- Screen reader users can't dismiss it
The fix:
<!-- ✅ Good: dialog role, accessible name, focus management -->
<div role="dialog" aria-modal="true" aria-labelledby="cookie-title">
<h2 id="cookie-title">Cookie consent</h2>
<p>We use cookies for analytics. You can accept all, reject non-essential, or customise.</p>
<button>Accept all</button>
<button>Reject non-essential</button>
<button>Customise</button>
</div>
If you use a CMP (Consent Management Platform) like Cookiebot, OneTrust, or Iubenda — verify they pass WCAG. Most of the big ones do in their default configuration; many self-built banners do not. One of the most common French enforcement issues in late 2025 was inaccessible cookie banners specifically.
What about the other 30%?
The ten above are catchable by automated tools. Roughly 30% of WCAG failures cannot be reliably automated:
- Whether
alt="Photo of CEO"is a meaningful description (versusalt="Anna Müller, CEO, smiling at camera") - Whether headings logically describe their sections
- Whether the reading order makes sense
- Whether content is understandable in plain language
- Whether forms recover gracefully from errors
These require either (1) a manual audit by an accessibility specialist or (2) at minimum, a test pass with a real screen reader (NVDA on Windows is free and the default for most blind users in Europe).
For most SMBs, the right move is: automated catches the bulk, then one half-day with a screen reader to verify the load-bearing flows (homepage → product → checkout → account).
On overlay widgets, again, with feeling
If your project manager or boss says "let's just install AccessiBe", here is the conversation:
- They do not fix the underlying HTML. Lawsuits and complaints proceed on the basis of the source code.
- They frequently break screen readers that are already configured on the user's device, because they layer a competing interaction model on top.
- They are not endorsed by EU regulators. France's enforcement actions specifically targeted source-level compliance.
- The dominant overlay vendor settled with the FTC in 2024 for misleading claims about "WCAG-compliance through one line of JavaScript."
There is no overlay shortcut that survives a complaint. Fix the source.
For a related deep-dive on the legal layers, see our WCAG vs EN 301 549 vs EAA breakdown. For the country-by-country fines that drive these complaints, see our EAA fines guide.
How many of these does your site have right now?
Webply runs a free WCAG 2.1 AA scan against your real templates, ranks issues by EAA legal risk, and gives you the developer-ready fix list. No credit card. No overlay widget.
Sources and further reading
- WebAIM Million 2024 Annual Accessibility Analysis — largest annual survey of web accessibility on the top 1 million homepages.
- Deque University axe-core 4.10 rules reference — documentation for every axe rule mentioned in this post.
- W3C — WCAG 2.1 Quick Reference and ARIA Authoring Practices.
- European Commission — Directive (EU) 2019/882 (EAA).
- France DGCCRF enforcement actions, November 2025 — Auchan, Carrefour, E.Leclerc, Picard; covered in our EAA fines analysis.
Code examples are illustrative and minimal — production code should be tested with real assistive technology in your specific framework. Not legal advice.