Photo by AbsolutVision on Unsplash
The EAA isn’t a vague legal idea anymore — it’s enforceable law, and regulators across the EU are already issuing warnings and opening investigations. This tutorial skips the legal jargon and gets straight to what matters to you: the specific things to check on your website, why they matter, and exactly how to fix them — whether you’re patching an existing site or building a new one from scratch.
📋 Table of Contents
- What the EAA Actually Requires (No Legal Jargon)
- Does This Apply to Your Website?
- The Standard You’re Actually Building Against: WCAG 2.1 AA
- Issue 1 — Keyboard Navigation Gaps
- Issue 2 — Missing or Bad Alt Text
- Issue 3 — Poor Color Contrast
- Issue 4 — Forms Without Proper Labels
- Issue 5 — Broken Heading Structure
- Issue 6 — Custom Components Missing ARIA
- Issue 7 — No Visible Focus Indicator
- Issue 8 — Inaccessible Error Messages
- Issue 9 — Videos Without Captions
- Issue 10 — Reflow & Zoom Failures
- The Accessibility Statement You Must Publish
- Where to Start: A Priority Roadmap
- Testing Workflow for Ongoing Compliance
- FAQ
What the EAA Actually Requires (No Legal Jargon)
Let’s cut through the legal language. The European Accessibility Act says: if you sell digital products or services to consumers in the EU, your website and apps need to be usable by people with disabilities — people who can’t see a screen well, can’t use a mouse, can’t hear audio, or process information differently.
It came into force on 28 June 2025. It’s enforced separately by each of the 27 EU member states, which means the exact fine amounts and enforcement style differ a bit by country — but the technical bar you need to clear is the same everywhere, because they all point back to the same standard.
Here’s the part that matters most to you as a developer: the EAA doesn’t invent new technical rules. It points to an existing, well-documented standard — EN 301 549 — which is built on top of WCAG 2.1 Level AA, a standard you can actually learn, test against, and build to. That’s good news. You don’t need to guess what “accessible” means; there’s a checklist.
Does This Apply to Your Website?
Before diving into fixes, it’s worth a quick gut-check on scope. You don’t need a lawyer for this — the lines are fairly clear for most everyday websites:
| Your situation | EAA scope? | Notes |
|---|---|---|
| E-commerce site selling to EU consumers | In scope | Even if your company is based outside the EU |
| Banking, payments, or fintech app | In scope | Explicitly named in the Act |
| Travel/transport booking platform | In scope | Ticketing and journey info both covered |
| Telecom or ISP customer portal | In scope | Includes account management interfaces |
| E-book or digital publishing platform | In scope | Covers reading apps and the content format itself |
| Pure B2B SaaS, no consumer signup | Likely out of scope | EAA targets consumer-facing services |
| Microenterprise (under 10 staff, <€2M turnover) | Partial exemption | Exempt from service rules, not product rules |
| Internal company tools / intranet | Out of scope | Not offered to consumers |
| Archived content not updated since before 28 Jun 2025 | Mostly exempt | Updating it brings it back into scope |
The Standard You’re Actually Building Against: WCAG 2.1 AA
When people say “EN 301 549 compliance,” what they mean in practice — for the parts of the site you build — is WCAG 2.1 Level AA. This is the version most EU regulators are checking against today, even though a future revision is expected to point to WCAG 2.2. Building to 2.2 now is a smart hedge, since it’s a superset of 2.1 with a handful of extra rules.
WCAG organizes everything around four principles, often remembered as POUR:
- Perceivable — can people sense the content, regardless of which sense they’re missing? (alt text, captions, contrast)
- Operable — can people navigate and interact, regardless of input method? (keyboard, no flashing, enough time)
- Understandable — is the content and interface predictable and clear? (labels, error messages, consistent navigation)
- Robust — does it work reliably with assistive technology? (valid HTML, correct ARIA)
Now let’s get into the actual issues. These ten cover the overwhelming majority of what regulators are flagging in real EAA enforcement cases so far — checkout flows, search, forms, and homepage navigation.
1. Keyboard Navigation Gaps
WCAG 2.1.1 (A) · 2.1.2 (A) · 2.4.7 (AA)
Why it matters: Around 1 in 14 people use a keyboard instead of a mouse to browse the web — due to motor impairments, screen reader use, or simply personal preference. If any part of your site only responds to mouse clicks or hover, those users hit a wall. This is one of the very first things accessibility auditors test, because it takes 30 seconds: unplug the mouse and press Tab.
The fix
Almost every keyboard issue traces back to one root cause: using a non-interactive element (a <div> or <span>) styled to look like a button, instead of an actual <button> or <a>. Native elements get keyboard support for free — your job is to stop reinventing them.
<div class="btn" onclick="submitForm()">
Submit
</div>
/* Tab skips right over this.
Enter/Space do nothing. */
<button type="button" class="btn" onclick="submitForm()">
Submit
</button>
/* Tab focuses it.
Enter and Space both activate it.
No extra JS required. */
If you genuinely must use a <div> for a custom widget (rare — think drag handles, custom sliders), you need to manually replicate what the browser gives buttons for free:
<div
class="custom-btn"
role="button"
tabindex="0"
id="custom-submit"
>Submit</div>
// You must add keyboard handling manually — buttons get this for free
document.getElementById('custom-submit').addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault(); // stop page scroll on Space
submitForm();
}
});
// 👆 Honestly — just use <button> instead and skip all of this.
- 01Replace clickable
<div>/<span>elements with<button>or<a href="..."> - 02Test your entire checkout/signup flow with the mouse unplugged — Tab, Shift+Tab, Enter, Space, Esc
- 03Check for “keyboard traps” — modals or widgets where Tab gets stuck and can’t escape
- 04Make sure the Tab order follows the visual reading order, not the DOM order if they differ (avoid
tabindexvalues above 0)
2. Missing or Bad Alt Text
WCAG 1.1.1 (A)
Why it matters: Screen reader users hear “image” with no further information when alt text is missing. For e-commerce specifically — exactly the sector EAA targets first — this means a blind shopper literally cannot tell what a product looks like or whether they’re looking at the right item.
The fix
The test to apply: “If this image disappeared, what information would the sighted user lose?” That answer is your alt text. Not a generic label, not the filename — the actual missing information.
<img src="prod-4471.jpg" alt="image">
<img src="logo.png" alt="logo.png">
<img src="chart.png" alt="">
<!-- (chart has real data — shouldn't be empty) -->
<img src="prod-4471.jpg"
alt="Navy wool peacoat, double-breasted, front view">
<a href="/">
<img src="logo.png" alt="Acme — return to homepage">
</a>
<img src="chart.png"
alt="Revenue grew from €40K in Jan to €98K in June 2025">
- 01Informative images (product photos, charts, diagrams) → describe the content/meaning
- 02Functional images (logo-as-link, icon buttons) → describe the action, not the image
- 03Decorative images (dividers, background flourishes) → use
alt=""so screen readers skip them entirely — this is correct, not lazy - 04For complex data visualizations, put the long description in nearby visible text or a
<figcaption>, not crammed into the alt attribute
3. Poor Color Contrast
WCAG 1.4.3 (AA) · 1.4.1 (A)
Why it matters: This is the single most common accessibility failure on the web — appearing on the vast majority of homepages scanned in annual accessibility surveys. Low-contrast text is unreadable for people with low vision, color blindness, or anyone using their phone in bright sunlight. It’s also the cheapest fix on this entire list.
The fix
WCAG AA requires a contrast ratio of 4.5:1 for normal text and 3:1 for large text (24px+, or 19px+ bold) against its background. You don’t need to calculate this by eye — use a tool.
/* ❌ Light grey on white — ratio ~2.85:1, fails AA */
.muted-text { color: #999999; background: #ffffff; }
/* ✅ Darker grey — ratio 4.6:1, passes AA */
.muted-text { color: #5c6675; background: #ffffff; }
/* ❌ Placeholder text often defaults to very low contrast */
input::placeholder { color: #cccccc; }
/* ✅ Bump it up — placeholders still need 4.5:1 */
input::placeholder { color: #767676; }
CSS prop=”prop” hint: color property name above renders correctly in the code block; just flagging for clarity since this is a text-only response.
- 01Install the free Colour Contrast Analyser (TPGi) or use the contrast checker built into Chrome DevTools’ color picker
- 02Check body text, placeholder text, disabled-but-still-readable states, and text over images/gradients — these get missed most often
- 03Never rely on color alone to show state (e.g., a red border for errors) — pair it with an icon or text label too (WCAG 1.4.1)
- 04Run axe DevTools across your key pages — contrast failures are flagged automatically and precisely
4. Forms Without Proper Labels
WCAG 1.3.1 (A) · 3.3.2 (A) · 4.1.2 (A)
Why it matters: Checkout, signup, and login forms are exactly what Sweden’s and the Netherlands’ regulators announced they’re prioritizing in their first compliance reviews. A form input with only a placeholder — no real label — is invisible to a screen reader once the user starts typing, because placeholders disappear.
The fix
Every input needs a real, programmatically connected <label>. The connection is made with matching for and id attributes — this is non-negotiable, not optional polish.
<input
type="email"
placeholder="Email address"
>
<!-- Disappears on focus.
No screen reader announcement.
Low contrast by default. -->
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
autocomplete="email"
>
<!-- Always visible.
Announced correctly.
Clicking the label focuses the input. -->
- 01Audit every form on your site — checkout, signup, login, newsletter, search — for missing
<label>elements - 02Group related fields (like a set of radio buttons) inside
<fieldset>with a<legend> - 03Add
autocompleteattributes (name,email,tel,street-address) — this helps users with motor and cognitive disabilities specifically, and it’s an explicit WCAG criterion (1.3.5) - 04Never use a CAPTCHA as the only way to verify a human — WCAG 3.3.8 requires an accessible alternative
5. Broken Heading Structure
WCAG 1.3.1 (A) · 2.4.6 (AA)
Why it matters: Screen reader users frequently jump between headings as a way of skimming a page — similar to how a sighted person scans visually for the section they want. If your headings skip levels or are chosen purely for font size rather than structure, that navigation breaks completely.
The fix
Headings exist to describe document structure, not to make text bigger. If you want bigger text without semantic meaning, use CSS — not a heading tag.
<h1>Product Name</h1>
<h4>Description</h4> <!-- skipped h2, h3 -->
<h2>Reviews</h2> <!-- now out of order -->
<h5>Related items</h5> <!-- skipped again -->
<h1>Product Name</h1>
<h2>Description</h2>
<h2>Reviews</h2>
<h3>Most helpful</h3>
<h2>Related items</h2>
/* Style with CSS, not by jumping heading levels */
- 01Exactly one
<h1>per page - 02Never skip a level going down (h2 → h4 is wrong; h2 → h3 is right)
- 03Use browser extensions like “HeadingsMap” to visualize your page’s outline and spot gaps instantly
- 04Add landmark regions too —
<nav>,<main>,<footer>— these let screen reader users jump straight to a section without reading headings at all
6. Custom Components Missing ARIA
WCAG 4.1.2 (A)
Why it matters: Modern frontends are full of custom-built dropdowns, modals, tabs, and accordions. None of these are native HTML elements, so the browser gives them zero built-in accessibility — you have to add it. Without it, a screen reader user has no idea a dropdown opened, a tab switched, or a modal appeared.
The fix
The golden rule of ARIA: use it to describe state, not to replace good HTML. A real <button> with aria-expanded beats a <div role="button"> every time.
<h3>
<button
type="button"
aria-expanded="false"
aria-controls="shipping-panel"
id="shipping-header"
>
Shipping & Returns
</button>
</h3>
<div
id="shipping-panel"
role="region"
aria-labelledby="shipping-header"
hidden
>
<!-- panel content -->
</div>
// JS: toggle aria-expanded AND the hidden attribute together
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !expanded);
panel.hidden = expanded;
});
- 01Modals need
role="dialog",aria-modal="true", a focus trap, and Escape-to-close - 02Dropdowns/menus need
aria-expandedon the trigger and arrow-key navigation between options - 03Live updates (cart count, search results, form errors) need
aria-live="polite"so screen readers announce the change automatically - 04Run every custom component past axe DevTools — incorrect ARIA usage is one of the easiest things for automated tools to catch
7. No Visible Focus Indicator
WCAG 2.4.7 (AA) · 2.4.11 (AA, new in 2.2)
Why it matters: If a keyboard user can’t see where focus currently is, keyboard navigation is technically possible but practically useless — they’re navigating blind. This is shockingly common because outline: none is a popular “quick fix” for designers who don’t like the default blue outline.
The fix
Never remove the focus outline without replacing it with something equally visible. The modern best practice uses :focus-visible, which shows the indicator for keyboard users but hides it for mouse clicks — giving you the best of both worlds.
/* ❌ NEVER do this site-wide */
*:focus { outline: none; }
/* ✅ Hide for mouse clicks, keep for keyboard navigation */
*:focus:not(:focus-visible) { outline: none; }
*:focus-visible {
outline: 3px solid #0b5fff;
outline-offset: 3px;
border-radius: 3px;
}
- 01Search your entire CSS codebase for
outline: noneoroutline: 0and verify each one has a:focus-visiblereplacement - 02Make sure the focus indicator has enough contrast against its background (same 3:1 ratio rules apply)
- 03Test on dark-themed sections separately — a dark blue outline can vanish on a dark background
8. Inaccessible Error Messages
WCAG 3.3.1 (A) · 3.3.3 (AA) · 4.1.3 (AA)
Why it matters: Checkout form validation is one of the highest-friction moments in any user journey, and it’s exactly where regulators have been testing. If errors are only shown as a red border with no text, or appear visually but aren’t announced to screen readers, those users are stuck — they know something’s wrong but not what or where.
The fix
Every error needs three things: it must be visible, described in text (not color alone), and announced to assistive technology when it appears dynamically.
<label for="card-number">Card number</label>
<input
type="text"
id="card-number"
aria-invalid="true"
aria-describedby="card-error"
>
<p id="card-error" role="alert">
<span aria-hidden="true">⚠</span>
Card number must be 16 digits.
</p>
// role="alert" makes screen readers announce this
// the moment it appears in the DOM — no extra JS needed
- 01Pair every error with an icon or text label — never rely on a red border alone
- 02Connect the error message to its input using
aria-describedby - 03Set
aria-invalid="true"on the input while it has an error - 04For a top-of-form error summary on submit, use
role="alert"oraria-live="assertive"so it’s announced immediately
9. Videos Without Captions
WCAG 1.2.2 (A) · 1.2.1 (A)
Why it matters: Captions aren’t just for deaf and hard-of-hearing users — they’re used constantly by people watching with sound off in public, non-native speakers, and anyone in a noisy environment. The EAA specifically calls out media accessibility as a requirement for sectors like travel, telecoms, and digital publishing.
The fix
For pre-recorded video, captions are achievable with native HTML — no special player required.
<video controls>
<source src="product-demo.mp4" type="video/mp4">
<track
kind="captions"
src="captions-en.vtt"
srclang="en"
label="English"
default
>
</video>
- 01Add a
.vttcaption file for every pre-recorded video using the native<track>element - 02For purely visual videos (no dialogue but important visual info), provide an audio description or a text transcript
- 03If you embed third-party video (YouTube, Vimeo), confirm captions are enabled and accurate — auto-generated captions often need manual correction
- 04Never autoplay audio — if you must autoplay video, mute it by default and give an obvious unmute control
10. Reflow & Zoom Failures
WCAG 1.4.10 (AA) · 1.4.4 (AA)
Why it matters: Many low-vision users browse with their browser zoomed to 200–400%. If your layout breaks, overlaps, or requires horizontal scrolling at that zoom level, the site becomes unusable for them — even though “technically” nothing crashed.
The fix
This usually comes down to fixed pixel widths and a layout that doesn’t use responsive units. Test by zooming your browser to 400% (Ctrl/Cmd + + several times) and checking for horizontal scrollbars or overlapping elements.
/* ❌ Fixed width breaks at high zoom */
.product-grid { width: 1200px; }
/* ✅ Responsive width with a sensible max */
.product-grid {
width: 100%;
max-width: 1200px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
/* ✅ Fluid type scales smoothly instead of breaking layout at fixed breakpoints */
h1 { font-size: clamp(1.5rem, 4vw, 2.5rem); }
- 01Replace fixed-pixel container widths with
%,max-width, and CSS Grid/Flexbox - 02Use relative units (
rem,em,clamp()) for font sizes so they scale with browser zoom and user font preferences - 03Test your three most important pages at 400% zoom in actual Chrome/Firefox — not just DevTools device emulation
- 04Make sure text can be resized up to 200% without assistive technology and without loss of content or function (WCAG 1.4.4)
The Accessibility Statement You Must Publish
This part isn’t code, but it’s a hard EAA requirement and frontend developers are often the ones who end up building the page for it. Every in-scope service provider must publish a public-facing accessibility statement describing how the service complies — or where it currently falls short.
Where to Start: A Priority Roadmap
You can’t fix everything in a day, and you shouldn’t try to. Here’s how to sequence the work for maximum impact with limited time — this mirrors what EU regulators themselves are checking first.
Checkout & account flows
Keyboard navigation, form labels, and error messages on signup, login, and checkout. This is exactly what French, Swedish, and Dutch regulators are checking first.
Color contrast sweep
Run axe DevTools across your top 10 pages. Contrast fixes are pure CSS — fast to fix, zero risk, immediate impact.
Alt text audit on product/content pages
Especially for e-commerce — product images need real, descriptive alt text, not filenames or “image”.
Heading structure & landmarks
Run HeadingsMap across your site. Fixing heading hierarchy is usually just changing tag names — low risk, high reward.
Custom component ARIA review
Modals, dropdowns, tabs, carousels. Higher effort since it often needs JS changes, not just markup.
Video captions & reflow testing
Caption files for existing video library; 400% zoom test across key templates.
Testing Workflow for Ongoing Compliance
Compliance isn’t a one-time audit you check off — new features can reintroduce old problems. Build a layered testing habit:
- 1Automated scan on every PR: wire axe-core into your CI pipeline so accessibility regressions get caught before merge, the same way you’d catch a broken unit test.
- 2Manual keyboard pass on new features: before marking any ticket “done,” tab through the new UI once. It takes under a minute and catches the most common failure on this entire list.
- 3Screen reader spot-check monthly: NVDA (free, Windows) or VoiceOver (built into Mac/iOS) on your three most-used flows.
- 4Real user testing periodically: automated tools catch roughly a third of issues — actual users with disabilities will surface things no tool can.
Frequently Asked Questions
Does the European Accessibility Act apply to my website?
If you sell products or services to consumers in the EU — e-commerce, banking, transport ticketing, telecoms, e-books, or related digital services — you are very likely in scope, regardless of where your company is based. Pure B2B services and microenterprises (fewer than 10 employees and under €2 million turnover) have partial exemptions, but consumer-facing platforms generally must comply.
What accessibility standard do I need to meet for EAA compliance?
The EAA does not name a single technical standard directly, but compliance is generally demonstrated through EN 301 549, the European ICT accessibility standard, which currently incorporates WCAG 2.1 Level AA. Building to WCAG 2.2 AA is a sensible forward-looking target, since a future revision of EN 301 549 is expected to reference it.
What happens if my website is not compliant?
Enforcement is handled by national authorities in each EU member state, and penalties vary by country. Most authorities are currently favoring warnings and a remediation period over immediate fines, but persistent non-compliance can lead to financial penalties, corrective orders, or restricted market access. Disability advocacy groups in several countries have also begun filing complaints and legal proceedings directly against non-compliant companies.
Do existing websites need to comply immediately?
Services already on the market before 28 June 2025 generally have until 28 June 2030 to fully comply, or sooner if the service undergoes a substantial update. New websites and services launched after 28 June 2025 must comply immediately. Either way, starting remediation now reduces both legal risk and the eventual cost of the work.
Where should a frontend developer start with EAA compliance?
Start with an automated scan using axe DevTools or Lighthouse to catch the easier 30–40% of issues, then manually test keyboard navigation and screen reader compatibility on your core user flows — search, checkout, account creation, and forms — since these are the areas EU regulators have explicitly said they are checking first.
Your EAA Action Plan
The European Accessibility Act turned “we should probably get to accessibility eventually” into a real deadline with real consequences. The good news: the technical bar is well-defined, well-documented, and entirely achievable with focused effort — there’s no ambiguity about what “accessible enough” means.
- This week: Run axe DevTools across your five highest-traffic pages and fix every contrast and missing-label issue it finds — these are quick, low-risk wins.
- This month: Tab through your entire checkout and signup flow with the mouse unplugged. Fix anything that doesn’t respond to keyboard input.
- This quarter: Wire axe-core into your CI pipeline so new code can’t quietly reintroduce old problems.
- Ongoing: Publish (and actually maintain) your accessibility statement, and schedule a recurring screen reader test on your core flows.
None of this requires reinventing your stack. It requires attention, the right testing habits, and treating accessibility as a standard part of “done” — the same way you already treat performance or security.
