DEV Community

Cover image for Building Accessible Web Applications: A Practical Guide for EAA Compliance
Dimitris Kiriakakis
Dimitris Kiriakakis

Posted on

Building Accessible Web Applications: A Practical Guide for EAA Compliance

Web accessibility is no longer just a best practice, it has now become a legal requirement in the European Union. With the European Accessibility Act (EAA) setting clear expectations for digital accessibility, developers must ensure their applications are inclusive for all users.

In this post, we’ll break down what EAA compliance means for developers, the legal risks of non-compliance, common accessibility (often abbreviated to A11y) mistakes, and practical steps to build better, more inclusive applications. We will review some A11y friendly libraries for modern frameworks and in the end you will get a repository that contains accessible web development practices in action.

Who needs to comply with the EAA?

The European Accessibility Act (Directive (EU) 2019/882) applies to companies that provide digital products and services in the EU. The deadline to comply is June 2025. The law covers:

  • E-commerce platforms: Online stores and digital marketplaces
  • Banking and financial services: Mobile banking apps, payment gateways
  • Streaming and media services: Video-on-demand platforms, digital libraries
  • Public sector and essential services: Government portals, healthcare websites
  • Software and SaaS products: Enterprise applications, productivity tools

Are there exemptions?
Micro-businesses (fewer than 10 employees and less than €2 million annual turnover) may be exempt, however accessibility improves user experience and market reach for all companies.

Legal consequences of non-compliance

Failing to meet EAA accessibility requirements comes with the following risks:

  • Fines and sanctions: Each EU country can enforce penalties, which can be costly.
  • Legal challenges: Consumers and advocacy groups can take legal action.
  • Market restrictions: Non-compliant services may be blocked from operating in the EU.

Beyond the legal aspect, inaccessible websites exclude millions of users and hurt usability for everyone. Again, addressing accessibility early on, prevents compliance issues and improves user experience for everyone.

Common accessibility mistakes (and how to fix them)

We developers, often make accessibility mistakes without realizing it. Below, we will outline common use cases and how to make them compliant and more accessible, categorized into must-have fixes for compliance and nice-to-have improvements for better usability.

Must-Haves: Essentials for accessibility compliance

1. Ensuring the page head is accessible

The <head> section of an HTML document is often overlooked, but it plays a crucial role in accessibility.

Must-have elements in the <head> section:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="An example of an accessible webpage compliant with the European Accessibility Act.">
    <title>Accessible Webpage Example</title>
    <link rel="stylesheet" href="styles.css">
</head>
Enter fullscreen mode Exit fullscreen mode
  • lang="en" on <html> – Specifies the primary language for screen readers.
  • Descriptive <title> – Helps users (especially those using screen readers) understand the page's purpose.
  • meta name="description" – Provides context when the page appears in search results.
  • meta name="viewport" – Ensures proper scaling and readability on mobile devices.

2. Using aria-labelledby for section headings

Screen readers should clearly associate form groups and section headings with their related content.

Correct usage:

<section id="contact" aria-labelledby="contact-heading">
    <h2 id="contact-heading">Contact Us</h2>
    <form>
       ...
    </form>
</section>
Enter fullscreen mode Exit fullscreen mode
  • aria-labelledby="contact-heading" – Ensures screen readers associate the heading with the section.

3. Adding proper labels and ARIA attributes to forms

Forms without labels are confusing for screen reader users because they don’t provide context for input fields. Additionally, users need real-time feedback when errors occur.

Example:

<input type="text" placeholder="Enter your name">
Enter fullscreen mode Exit fullscreen mode

Accessible implementation:

<label for="name">Name:</label>
<input 
  type="text" 
  id="name" 
  name="name" 
  placeholder="Enter your name" 
  required 
  aria-invalid="false"
  aria-describedby="name-error">
<span id="name-error" class="error-message" aria-live="polite"></span>
Enter fullscreen mode Exit fullscreen mode
  • aria-invalid="false" – Indicates that the field is valid (should be set to "true" when an error occurs).
  • aria-describedby="name-error" – Links the input field to an error message.
  • aria-live="polite" – Ensures error messages are announced dynamically by screen readers.

Handling validation dynamically with VanillaJS:

const nameInput = document.getElementById("name");
const nameError = document.getElementById("name-error");

nameInput.addEventListener("input", () => {
    if (nameInput.value.trim() === "") {
        nameInput.setAttribute("aria-invalid", "true");
        nameError.textContent = "Name is required.";
    } else {
        nameInput.setAttribute("aria-invalid", "false");
        nameError.textContent = "";
    }
});
Enter fullscreen mode Exit fullscreen mode

4. Important state changes announcements

Users should be informed about important state changes on the UI, for instance whether their password is currently visible or hidden.

Common use case:

<input type="password" id="password">
<button onclick="togglePassword()">Show</button>
Enter fullscreen mode Exit fullscreen mode

Accessible implementation:

<label for="password">Password:</label>
<div style="position: relative;">
    <input 
        type="password" 
        id="password" 
        name="password" 
        required 
        aria-invalid="false" 
        aria-describedby="password-visibility">
    <button 
        type="button" 
        id="toggle-password-visibility" 
        aria-label="Show password">
        Show
    </button>
</div>
<span id="password-visibility" class="visually-hidden" aria-live="polite">
    Password is currently hidden.
</span>
Enter fullscreen mode Exit fullscreen mode

VanillaJS to update the state dynamically:

const passwordField = document.getElementById("password");
const toggleButton = document.getElementById("toggle-password-visibility");
const visibilityMessage = document.getElementById("password-visibility");
toggleButton.addEventListener("click", () => {
    const isPasswordVisible = passwordField.type === "text";
    passwordField.type = isPasswordVisible ? "password" : "text";

    toggleButton.textContent = isPasswordVisible ? "Show" : "Hide";
    visibilityMessage.textContent = isPasswordVisible 
        ? "Password is currently hidden." 
        : "Password is currently visible.";
});
Enter fullscreen mode Exit fullscreen mode

This ensures that screen readers will announce the password state change properly.

5. Providing visible focus indicators

Keyboard users need to see where their focus is, but sometimes we remove default focus styles for aesthetic reasons.

Common mistake:

button:focus {
  outline: none;
}
Enter fullscreen mode Exit fullscreen mode

Correct usage:

button:focus {
  outline: 3px solid #ffcc00;
}
Enter fullscreen mode Exit fullscreen mode

For a better visual experience, we could as well use :focus-visible to make focus styles appear only when navigating via keyboard:

button:focus-visible {
  outline: 3px solid #ffcc00;
}
Enter fullscreen mode Exit fullscreen mode

6. Missing or incorrect alt text for images

Screen readers rely on alt attributes to describe images to visually impaired users. Without them, images are either ignored or announced as "image," providing no useful context.

Common mistake:

<img src="team.webp">
Enter fullscreen mode Exit fullscreen mode

Correct usage:

<img src="team.webp" alt="A diverse team of eight people smiling in an office">
Enter fullscreen mode Exit fullscreen mode

If an image is decorative and does not add meaningful content, use alt="" so screen readers ignore it.

7. Providing video captions for multimedia content

Video content must include captions to ensure accessibility for users who are deaf or hard of hearing.

Incorrect usage:

<video controls>
  <source src="intro.mp4" type="video/mp4">
</video>
Enter fullscreen mode Exit fullscreen mode

Correct usage:

<video controls>
  <source src="intro.mp4" type="video/mp4">
  <track src="captions.vtt" kind="subtitles" srclang="en" label="English">
</video>
Enter fullscreen mode Exit fullscreen mode

You can find an example of a captions file here.

8. Ensuring keyboard navigation support

Native interactive elements like <button>, <a href="">, <input>, <select>, and <textarea> are automatically focusable and included in the natural tab order. So we would not need to add tabindex to a <button>, since it is already keyboard-focusable.

However, in case we would need to make non-interactive elements like <div>, <span>, or <p> keyboard-focusable, we would need to use tabindex=n.

Example:

<span 
    role="button">
    I am a custom button!
</span>
Enter fullscreen mode Exit fullscreen mode

Accessible implementation:

<span 
    role="button" 
    tabindex="0">
    I am a custom button!
</span>
Enter fullscreen mode Exit fullscreen mode

Here, a static HTML element (<span>) is used to mark up the control, and is exposed as such to assistive technologies through the application of role="button". However, because it is marked up using a <span> element, it won't be focusable without tabindex="0".

Nice-to-haves: Further enhancements

1. Announcing state changes with ARIA live sections

For example, users should receive feedback when a form submission succeeds or fails.

Use case:

<p id="form-status">Form submitted successfully!</p>
Enter fullscreen mode Exit fullscreen mode

Accessible implementation:

<!-- idle state -->
<p id="form-status" role="status" aria-live="polite"></p>

<!-- when form has been submitted -->
<p id="form-status" role="status" aria-live="polite">
  Form submitted successfully!
</p>
Enter fullscreen mode Exit fullscreen mode
  • aria-live="polite" – Ensures the message is announced by screen readers. When aria-live's attribute is set to polite, assistive technologies will notify users of updates but generally do not interrupt the current task, with the updates having a low priority. When set to assertive, assistive technologies immediately notify the user, potentially clearing the speech queue of previous updates.
  • role="status" – Indicates that this element provides status updates.

2. Reducing motion for users with vestibular disorders

Animations can cause discomfort for users sensitive to motion.

Correct approach:

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none;
    transition: none;
  }
}
Enter fullscreen mode Exit fullscreen mode

This respects the user’s system settings for reduced motion.

Accessibility-friendly libraries and tools in modern frameworks

Angular

For Angular applications, consider using:

  • @angular/cdk/a11y – In latest Angular versions the Component Development Kit (CDK) includes the a11y package. We can find some useful examples and usages at the official documentation.
  • Angular Material — All Material components follow accessibility best practices.

React

For React applications, these libraries can prove really helpful:

  • @react-aria (Adobe’s React Aria) – Provides UI primitives that enable us to build accessible components.
  • React Helmet — Helps manage document structure and metadata for screen readers e.g. provide a unique title for each page or view.

Testing & Validating Accessibility

To ensure and maintain accessibility, our websites should be tested using a combination of automated tools and real user feedback.

1. Automated Testing

A11y report for the eaa-compliance repo

  • Lighthouse (Chrome DevTools): With the accessibility report we can run audits to detect WCAG violations.
  • axe DevTools (Browser Extension): Provides detailed accessibility reports.

2. Screen Reader Testing

  • VoiceOver (Mac) — Press CMD + F5 to activate.
  • NVDA (Windows) — Free and widely used screen reader.

3. Keyboard Navigation Testing

  • Use TAB and SHIFT + TAB to navigate through your websites and your forms.
  • Ensure all interactive elements are accessible and state changes are being announced.

4. User Testing

  • Test with different accessibility settings (high contrast mode, reduced motion).
  • Engage users with disabilities for real-world feedback.

Final Thoughts: Accessibility as a Continuous Effort

Accessibility isn’t a single task, it should be an ongoing commitment to creating better digital experiences for everyone.

  • Start with must-have fixes to meet compliance requirements.
  • Implement nice-to-have improvements to enhance usability.
  • Continuously test and iterate to maintain accessibility standards.
  • By prioritizing accessibility, we can build applications that are not only legally compliant but also more inclusive and user-friendly.

In case you are looking for more real-world examples, I’ve recently put together a repository that contains accessible web development practices in action.

GitHub logo dimeloper / eaa-compliance-examples

This repository contains examples of accessible web development practices to help developers comply with the European Accessibility Act (EAA).

EAA Compliance Examples

This repository contains examples of accessible web development practices to help developers comply with the European Accessibility Act (EAA). The examples demonstrate techniques for creating inclusive, user-friendly webpages for all users.

Features

  • Keyboard Accessibility: Includes skip links and focus indicators.
  • Accessible Forms: Dynamically validates inputs with error messages announced to assistive technologies.
  • Password Field Enhancements: Visibility toggle with live announcements and error handling.
  • Multimedia Accessibility: Provides captions for videos and alt text for images.
  • Semantic HTML: Uses appropriate elements for structure and navigation.
  • Live Regions: Dynamic updates for screen readers using ARIA.

File Structure

eaa-compliance-examples/
├── index.html           # Main HTML document
├── styles.css           # External CSS for styling
├── script.js            # JavaScript for form interactions
├── captions/
│   └── captions.vtt     # Example captions file for the video
├── intro.mp4            # Introductory video showcasing inclusivity
├── README.md            # Documentation for the

Top comments (0)