EAA WCAG 2.1 AA Implementation: Technical Requirements for EU Web Services
Post #2 in the sota.io EU-EAA-DEVELOPER-2026 Series
The European Accessibility Act (EAA) — Directive (EU) 2019/882 — does not specify accessibility requirements directly. Instead, it mandates conformance with EN 301 549 v3.2.1, the harmonised European standard that incorporates WCAG 2.1 Level AA as its core technical reference for web-based services.
This post is the practical developer companion to our EAA overview. Where that guide covered scope and enforcement, this guide is code-level: which success criteria apply, what they actually mean for SaaS UIs, and how to build a conformant accessibility stack.
EN 301 549 and WCAG 2.1: The Technical Chain
Understanding the relationship between standards is essential before you write a single line of accessible code:
EAA Directive 2019/882
↓ references harmonised standard
EN 301 549 v3.2.1 (2021)
↓ Chapter 9 "Web" incorporates
WCAG 2.1 (W3C Recommendation, June 2018)
↓ at conformance level
Level AA (50 success criteria)
EN 301 549 is the European standard for ICT accessibility. Chapter 9 covers web content and applications, and it directly adopts WCAG 2.1 Level AA as the required technical baseline. Chapters 11 (Software), 12 (Documentation), and 13 (ICT providing relay or emergency service access) add further requirements, but for most SaaS web applications, WCAG 2.1 AA through Chapter 9 is the primary obligation.
Practical implication: If you build WCAG 2.1 AA conformance into your product, you're meeting the technical core of the EAA. The additional EN 301 549 chapters add non-web-content requirements (authoring tools, documentation, customer support) that many SaaS products also need to address.
The POUR Framework: WCAG's Four Principles
WCAG 2.1 organises all success criteria under four principles. Every SaaS team should understand these before diving into individual criteria:
Principle 1: Perceivable
Users must be able to perceive all information and UI components. Content cannot be invisible to all their senses.
Core requirements:
- Text alternatives for non-text content (images, icons, controls)
- Captions and transcripts for audio/video
- Content that can be presented in different ways (e.g., screen readers) without losing meaning
- Content that is easy to see and hear — sufficient contrast, no information conveyed by colour alone
Principle 2: Operable
Users must be able to operate all interface components and navigation.
Core requirements:
- All functionality available via keyboard (no mouse required)
- Users have enough time to read and use content
- No content that could cause seizures or physical reactions
- Users can navigate, find content, and determine where they are
Principle 3: Understandable
Users must be able to understand both the information and the UI operation.
Core requirements:
- Text is readable and understandable
- Pages appear and operate in predictable ways
- Users are helped to avoid and correct mistakes
Principle 4: Robust
Content must be robust enough to be reliably interpreted by assistive technologies.
Core requirements:
- Content compatible with current and future user agents and assistive technologies
- Status messages communicated to assistive technologies without receiving focus
The 50 Level AA Success Criteria by Category
This is the exhaustive list of what you must implement. Level A (must) + Level AA (should, but mandated by EAA) = 50 criteria total.
Perceivable — Level A (must implement)
1.1.1 Non-text Content (A): Every image, icon, button, chart, and decorative element needs a text alternative. Icons that convey meaning need aria-label. Decorative images need alt="". Charts need descriptions.
<!-- Image with meaning -->
<img src="error-icon.svg" alt="Error: form submission failed" />
<!-- Decorative image -->
<img src="background-texture.png" alt="" role="presentation" />
<!-- Icon button -->
<button aria-label="Delete item">
<svg aria-hidden="true"><!-- icon SVG --></svg>
</button>
1.2.1 Audio-only and Video-only (A): If you have product demo videos or audio announcements, provide alternatives. Pre-recorded audio needs a transcript. Pre-recorded video-only needs audio description or text alternative.
1.2.2 Captions (Pre-recorded) (A): All pre-recorded video with audio must have accurate captions. YouTube's auto-captions do not meet this requirement — they must be reviewed and corrected.
1.2.3 Audio Description or Media Alternative (A): Pre-recorded video must either have an audio description track or a full text alternative that presents equivalent information.
1.3.1 Info and Relationships (A): Structure conveyed visually must also be conveyed programmatically. The most commonly failed criterion in SaaS UIs:
<!-- WRONG: visual table that isn't semantic -->
<div class="table">
<div class="row header"><div>Name</div><div>Status</div></div>
<div class="row"><div>Project A</div><div>Active</div></div>
</div>
<!-- RIGHT: semantic table -->
<table>
<thead>
<tr><th scope="col">Name</th><th scope="col">Status</th></tr>
</thead>
<tbody>
<tr><td>Project A</td><td>Active</td></tr>
</tbody>
</table>
Form labels, heading hierarchy, list structure — all must be semantically correct.
1.3.2 Meaningful Sequence (A): Reading order must make sense when CSS is removed. Modal dialogs that appear at the end of the DOM but visually overlay the page are a common failure.
1.3.3 Sensory Characteristics (A): Instructions cannot rely solely on shape, colour, size, visual location, orientation, or sound. "Click the red button" fails. "Click the Submit button" passes.
1.4.1 Use of Colour (A): Colour cannot be the only visual means of conveying information. Status indicators that use only red/green need a secondary indicator (icon, text, pattern).
<!-- WRONG: colour only -->
<span class="status-red">Failed</span>
<!-- RIGHT: colour + text/icon -->
<span class="status-red" aria-label="Status: Failed">
<svg aria-hidden="true"><!-- x-circle icon --></svg>
Failed
</span>
1.4.2 Audio Control (A): If audio plays automatically for more than 3 seconds, provide a mechanism to pause, stop, or control volume independent of the system volume.
Perceivable — Level AA (EAA mandated)
1.2.4 Captions (Live) (AA): Live video streams (webinars, product demos) must have live captions. This is complex to implement but required for in-scope live broadcasts.
1.2.5 Audio Description (Pre-recorded) (AA): All pre-recorded video must have audio descriptions for visual content that is not audible (actions, text on screen, scene descriptions).
1.3.4 Orientation (AA): Content must not restrict its view and operation to a single display orientation, unless a specific orientation is essential. Lock your UI to portrait/landscape only if it's functionally necessary.
1.3.5 Identify Input Purpose (AA): Input fields collecting user data must identify their purpose via autocomplete attributes. This helps users with cognitive disabilities and autofill:
<input type="email" name="email" autocomplete="email" />
<input type="text" name="name" autocomplete="name" />
<input type="tel" name="phone" autocomplete="tel" />
1.4.3 Contrast (Minimum) (AA): Normal text (below 18pt / 14pt bold) needs a 4.5:1 contrast ratio against background. Large text needs 3:1. This fails frequently in dark-mode SaaS UIs with low-contrast secondary text.
Body text #6b7280 on #ffffff = 4.05:1 — FAIL
Body text #6b7280 on #f9fafb = 3.89:1 — FAIL
Body text #374151 on #ffffff = 8.59:1 — PASS
1.4.4 Resize Text (AA): Text must be resizable up to 200% without assistive technology and without loss of content or functionality. Avoid px-fixed font sizes; use rem or em.
1.4.5 Images of Text (AA): Avoid using images to display text that could be rendered as actual text. Logos are exempt; "Join 10,000+ developers" banners rendered as PNG are not.
1.4.10 Reflow (AA): Content must be presentable without horizontal scrolling at a viewport width equivalent to 320 CSS pixels (400% zoom on a 1280px display). This is the single most commonly failed criterion in enterprise SaaS.
/* Horizontal scroll is the failure mode */
/* Avoid fixed widths; use max-width and flexible layouts */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
1.4.11 Non-text Contrast (AA): UI components (input borders, focus indicators, chart series) and informational graphics need a 3:1 contrast ratio. Thin border-color: #d1d5db on white backgrounds commonly fails.
1.4.12 Text Spacing (AA): Users must be able to apply custom text spacing (line height 1.5×, letter spacing 0.12em, word spacing 0.16em, paragraph spacing 2em) without loss of content or functionality. Test with the Text Spacing Bookmarklet.
1.4.13 Content on Hover or Focus (AA): Custom tooltips and hover content that appears must be: dismissible (Escape key), hoverable (pointer can move to the tooltip), and persistent (content does not disappear if pointer moves off trigger).
Operable — Level A
2.1.1 Keyboard (A): All functionality available by mouse must be available by keyboard. Every interactive element must be reachable and operable via Tab/Shift-Tab/Enter/Space/arrow keys. Custom widgets (date pickers, drag-and-drop, rich text editors) are the common failure points.
2.1.2 No Keyboard Trap (A): Keyboard focus must never be trapped in a component where there is no escape (except modals, which must trap focus intentionally and provide an explicit dismiss mechanism). Inline iframes with no keyboard escape are a frequent violation.
2.1.4 Character Key Shortcuts (A): Single-key shortcuts activated without modifier keys must be remappable, disableable, or only active when a component has focus.
2.2.1 Timing Adjustable (A): If time limits exist (session timeouts, auto-submitting forms), users must be able to turn them off, adjust them, or get at least 20 seconds to extend them.
2.2.2 Pause, Stop, Hide (A): Moving, blinking, scrolling content that starts automatically and lasts more than 5 seconds must be pausable or stoppable.
2.3.1 Three Flashes or Below Threshold (A): No content that flashes more than three times per second. This is a hard safety requirement, not just an accessibility preference.
2.4.1 Bypass Blocks (A): Provide a mechanism to skip repeated blocks of navigation (skip-to-main-content link). This must be the first focusable element in the page.
<a class="skip-link" href="#main-content">Skip to main content</a>
<nav><!-- navigation --></nav>
<main id="main-content"><!-- content --></main>
2.4.2 Page Titled (A): Each page must have a descriptive <title> that identifies the page and site. "Dashboard | sota.io" is better than "Dashboard". "Untitled" fails.
2.4.3 Focus Order (A): Navigation order must be logical and meaningful — top to bottom, left to right in LTR layouts. DOM order determines focus order; visual CSS reordering (order, flex-direction: row-reverse) can break this.
2.4.4 Link Purpose (In Context) (A): The purpose of every link must be determinable from link text or link context. "Click here" and "Read more" fail unless the surrounding context makes the destination clear.
2.5.1 Pointer Gestures (A): All functionality using multipoint or path-based gestures must be operable with a single pointer. Pinch-to-zoom, swipe gestures, and draw-to-interact must have single-click/tap alternatives.
2.5.2 Pointer Cancellation (A): For single-pointer activation, at least one of: no down-event activation, abort/undo, up-event activation, or essential exception. This prevents accidental activations.
2.5.3 Label in Name (A): Accessible name (aria-label) must contain the visible text label. If a button says "Submit", aria-label="Submit form data" is fine. aria-label="Go" when the button says "Submit" fails.
2.5.4 Motion Actuation (A): Functionality triggered by device motion (shake to undo, tilt to scroll) must have UI alternatives and the motion trigger must be disableable.
Operable — Level AA
2.4.5 Multiple Ways (AA): More than one way must exist to locate a page within a set of pages — navigation menu + site search + breadcrumbs, or sitemap. An app with only a linear wizard flow and no navigation fails this.
2.4.6 Headings and Labels (AA): Headings and labels must describe the topic or purpose. "Section 3" fails. "User Permissions Configuration" passes.
2.4.7 Focus Visible (AA): Any keyboard-operable UI must have a visible keyboard focus indicator. Removing :focus outlines with outline: none without a replacement is one of the most common accessibility bugs in SaaS design systems.
/* WRONG: removes all focus indicators */
* { outline: none; }
/* RIGHT: visible custom focus style */
:focus-visible {
outline: 2px solid #10b981;
outline-offset: 2px;
}
2.5.6 Concurrent Input Mechanisms (AA): Content must not restrict input mechanisms available to the platform. Don't lock users to touch-only or mouse-only interaction modes.
Understandable — Level A
3.1.1 Language of Page (A): The default language of the page must be declared in <html lang="en">. Missing or wrong lang attribute causes screen readers to pronounce text incorrectly.
3.2.1 On Focus (A): Receiving focus must not cause a context change (navigation, form submission, popup). Removing focus from an input must not submit a form.
3.2.2 On Input (A): Changing the value of a UI component must not automatically change context unless the user was advised in advance.
3.3.1 Error Identification (A): Errors must be identified in text and described to the user. Red border alone is insufficient — the field must have error text associated via aria-describedby.
<div>
<label for="email">Email address</label>
<input id="email" type="email" aria-describedby="email-error" aria-invalid="true" />
<span id="email-error" role="alert">Please enter a valid email address.</span>
</div>
3.3.2 Labels or Instructions (A): When user input is required, labels or instructions must be provided. Placeholder text alone is not a label — it disappears on input and has insufficient contrast.
Understandable — Level AA
3.1.2 Language of Parts (AA): Passages in a different language must be identified with lang attribute. If your SaaS has multilingual content blocks, annotate them.
3.2.3 Consistent Navigation (AA): Navigation mechanisms repeated across pages must occur in the same relative order — don't rearrange the sidebar or header navigation between pages.
3.2.4 Consistent Identification (AA): Components with the same functionality across pages must be consistently identified. The logout button's accessible name must be consistent everywhere it appears.
3.3.3 Error Suggestion (AA): When an error is detected, provide suggestions for correction where possible (without compromising security). "Password must be 8+ characters" is better than "Invalid password".
3.3.4 Error Prevention (Legal, Financial, Data) (AA): For submissions with legal consequences, financial transactions, or data modification that cannot be recovered: provide at least one of — reversibility (undo), checking (review before submit), or confirmation (explicit confirm step).
Robust — Level A
4.1.1 Parsing (A): HTML must be parseable — unique IDs, properly nested elements, complete start/end tags. Run your HTML through the W3C Validator.
4.1.2 Name, Role, Value (A): All UI components must expose their name, role, and value to assistive technologies. Custom components built from div and span must use ARIA roles:
<!-- WRONG: div masquerading as a button -->
<div onclick="submit()" class="btn-primary">Submit</div>
<!-- RIGHT: semantic HTML -->
<button type="submit">Submit</button>
<!-- RIGHT: ARIA if semantic HTML is not possible -->
<div role="button" tabindex="0" onclick="submit()"
onkeydown="handleKey(event)" class="btn-primary">Submit</div>
Robust — Level AA
4.1.3 Status Messages (AA): Status messages (form submission confirmation, error count, progress updates) must be programmatically determinable without receiving focus, using role="alert", role="status", or aria-live regions.
<!-- Success notification that doesn't steal focus -->
<div role="status" aria-live="polite" aria-atomic="true">
Project saved successfully.
</div>
<!-- Error alert that interrupts (use sparingly) -->
<div role="alert" aria-live="assertive" aria-atomic="true">
3 validation errors. Please correct them before continuing.
</div>
Common SaaS Failure Patterns (Ranked by Frequency)
Based on accessibility audits of typical SaaS applications, these are the most common violations:
| Rank | Criterion | Common Cause |
|---|---|---|
| 1 | 1.4.3 Contrast | Low-contrast secondary text, disabled state colours |
| 2 | 4.1.2 Name/Role/Value | Custom components without ARIA, icon buttons without labels |
| 3 | 1.3.1 Info and Relationships | Visual-only tables, non-semantic form structure |
| 4 | 2.4.7 Focus Visible | outline: none in design system reset |
| 5 | 1.4.10 Reflow | Fixed-width sidebars, non-responsive data tables |
| 6 | 2.1.1 Keyboard | Drag-and-drop without keyboard alternative |
| 7 | 4.1.1 Parsing | Duplicate IDs, broken ARIA references |
| 8 | 1.3.5 Identify Input Purpose | Missing autocomplete attributes |
| 9 | 3.3.1 Error Identification | Colour-only error state |
| 10 | 4.1.3 Status Messages | Toast notifications without aria-live |
The Minimum Viable Accessibility Stack
For a SaaS team starting from scratch, implement in this priority order:
Phase 1: Structural Fixes (Week 1–2)
These eliminate the majority of screen reader failures:
- Semantic HTML audit: Replace
div/spanbuttons withbutton, ensure heading hierarchy (h1→h2→h3), usenav,main,header,footerlandmark elements - Skip link: Add
<a class="skip-link" href="#main">Skip to main content</a>as first focusable element - Form labels: Audit every form input for programmatic label association (
for/idpairing oraria-labelledby) - Image alt text: Audit all images — meaningful alt text for informative images, empty alt for decorative
- Page titles: Ensure unique, descriptive
<title>on every page/route
Phase 2: Visual Fixes (Week 3–4)
- Contrast audit: Run all text and UI component colours through a contrast checker — target 4.5:1 for body text, 3:1 for large text and UI components
- Focus indicators: Add visible
:focus-visiblestyles to your design system — at minimumoutline: 2px solid <brand-colour> - Colour-only indicators: Add secondary indicators (icons, text) to all status-only colour coding
Phase 3: Interactive Fixes (Week 5–8)
- Keyboard navigation audit: Tab through your entire app with mouse disabled — every interaction must be reachable
- ARIA on custom components: Date pickers, modals, dropdowns, data grids — all need proper ARIA roles, states, and properties
aria-liveregions: Add status announcements for async operations, form submissions, real-time updates- Error handling: Implement programmatic error association with
aria-describedbyandaria-invalid
Phase 4: Remaining Criteria (Ongoing)
Orientation, language declarations, autocomplete attributes, motion reduction (prefers-reduced-motion), and the detailed interaction patterns.
The Accessibility Statement: What the EAA Requires
Under Article 13(2) of Directive 2019/882 (as transposed in national law), service providers must publish an accessibility statement. The statement must include:
- The services covered
- The applicable standard (EN 301 549 v3.2.1 / WCAG 2.1 AA)
- Conformance status: Fully conformant / Partially conformant / Non-conformant
- Known non-conformances with reasons and alternatives
- A feedback mechanism (email or form where users can report barriers)
- An enforcement contact (relevant National Competent Authority)
- The date of the statement and last review date
Template minimum viable statement:
Accessibility Statement for [Service Name]
[Company] is committed to ensuring digital accessibility in line with
Directive (EU) 2019/882 (European Accessibility Act) and EN 301 549 v3.2.1.
Conformance status: Partially conformant. [Service] partially conforms
to WCAG 2.1 Level AA. The following content does not fully conform:
- [List known issues]
Feedback: To report accessibility issues, email accessibility@[domain]
or use our feedback form at [URL].
Enforcement: If you are unsatisfied with our response, contact [NCA name
and URL].
This statement was last reviewed on [date].
The statement must be publicly accessible — link it from your product footer and <head> (<link rel="accessibility" href="/accessibility">).
Testing Methodology
A conformant EAA submission requires both automated and manual testing:
Automated Tools (Catch ~30–40% of issues)
| Tool | Type | Best For |
|---|---|---|
| axe-core | Library | CI integration |
| axe DevTools | Browser extension | Development |
| Lighthouse Accessibility | Browser/CI | Score tracking |
| WAVE | Browser extension | Visual review |
| Pa11y | CLI | Batch URL testing |
// package.json — add to test suite
{
"scripts": {
"test:a11y": "pa11y-ci --sitemap https://yourapp.com/sitemap.xml"
}
}
Manual Testing (Catch the other 60%)
- Keyboard-only navigation: Unplug mouse, navigate your app using only Tab, Shift-Tab, Enter, Space, and arrow keys
- Screen reader testing:
- Windows: NVDA (free) + Chrome, JAWS + Chrome
- macOS: VoiceOver (built-in) + Safari
- iOS: VoiceOver + Safari
- Android: TalkBack + Chrome
- 200% zoom test: Browser zoom to 200%, confirm no content is cut off
- 400% zoom / reflow test: Set viewport to 320px equivalent, verify no horizontal scroll for content
- Colour contrast verification: Use browser dev tools' accessibility inspector or Colour Contrast Analyser
- Text spacing test: Apply the Text Spacing Bookmarklet
CI/CD Integration
# GitHub Actions — accessibility check on PR
name: Accessibility Tests
on: [pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run build
- run: npx pa11y-ci ./test-urls.txt
React/Vue/Angular: Framework-Specific Guidance
React
- Use
react-aria(Adobe) orheadlessui(Tailwind Labs) for accessible component primitives - Set
roleandaria-*as regular JSX props - Use
@axe-core/reactfor development-time accessibility checking:
if (process.env.NODE_ENV === 'development') {
const axe = await import('@axe-core/react')
axe.default(React, ReactDOM, 1000)
}
Vue
vue-announcerfor programmaticaria-liveannouncementsVFocusdirective for focus management in modals and route transitions
Angular
- Angular CDK's
a11ymodule —LiveAnnouncer,FocusTrap,FocusMonitor - Enable Angular's accessibility checks in the Material design system
What Qualifies as Disproportionate Burden
Article 14 of Directive 2019/882 allows service providers to invoke disproportionate burden as a justification for not implementing specific requirements. This is not a blanket exemption — it must be assessed per requirement, documented, and reviewed every five years.
The assessment must weigh:
- The size and resources of the organisation
- The estimated cost of implementing accessibility against the overall cost of the service
- The potential benefit to persons with disabilities
Important: This exemption does not apply to fundamental accessibility obligations (basic structural markup, keyboard navigation, alt text for primary content). It is intended for edge cases like legacy embedded third-party content or highly specialised technical tools.
Enforcement Timeline and Risk Assessment
The EAA has been enforceable since June 28, 2025. Current NCA enforcement posture by sector:
| Country | NCA | Enforcement Status |
|---|---|---|
| Germany | BMAS / Bundesfachstelle | Active — BFSG transposition, formal complaints accepted |
| France | DINUM | Active — RGAA references updated to EN 301 549 |
| Netherlands | Accessibility Foundation | Active — enforcement via complaints |
| Ireland | DCEDIY | Active — monitoring phase |
| Sweden | DIGG | Active — audit programme running |
Complaint-driven enforcement is the current pattern — NCAs respond to complaints from users, disability organisations, and advocacy groups. Proactive audits are increasing in 2026 as the one-year post-application deadline passed.
Risk profile for non-compliant SaaS:
- NCAs can issue improvement notices with deadlines (typically 30–90 days)
- Repeat non-compliance: fines up to €100,000 in some member states (Germany BFSG: up to €100,000 per violation)
- Reputational risk: public NCA enforcement registers
- Procurement risk: public sector buyers increasingly require EAA conformance attestation
Next in This Series
In the next post, we'll cover EAA Mobile App Accessibility — iOS, Android, and Progressive Web App requirements under Directive 2019/882, including WCAG 2.1 AA application to native mobile components and the specific PWA considerations that many teams overlook.
Hosting a SaaS with strict EU data residency requirements? sota.io is EU-native managed PaaS — deploy from git, 100% GDPR, Hetzner Germany, no CLOUD Act exposure.
EU-Native Hosting
Ready to move to EU-sovereign infrastructure?
sota.io is a German-hosted PaaS — no CLOUD Act exposure, no US jurisdiction, full GDPR compliance by design. Deploy your first app in minutes.