Privacy Policy© 2026 DEV BAK - TECH BLOG. All rights reserved.
DEV BAK - TECH BLOG
frontend

15 Frontend Accessibility Checklist Items After EAA Takes Effect (European Accessibility Act WCAG 2.1 AA Standard)

On June 28, 2025, I stopped while reading a message that came through on Slack: "EAA has taken effect — is our service okay?" At first I thought, "It's an EU law, so it must be about companies doing business directly in Europe," but after reading the extraterritorial provisions, my thinking changed. If you're providing services to EU customers, it applies to you regardless of whether your headquarters is in Seoul or New York. If you have more than 10 employees or annual revenue exceeding 2 million euros, our service is covered. If you run a global SaaS, an English-language e-commerce site, or a service with traffic coming in from the EU, it's worth reconsidering the assumption that "we'll be fine."

Automated accessibility tools only catch 30–40% of all issues. The remaining 60–70% are things you can only see by looking directly at the code. In this article, I've compiled a checklist of the 15 items most commonly missed in practice, based on WCAG 2.1 AA standards. After reading, you'll also walk away with a 3-step action plan you can execute today.

Since the code examples are centered on HTML/CSS/React, this article focuses on the frontend domain. Backend or full-stack developers can use it as a reference to understand "this is what our service's frontend should look like."


Table of Contents

  • Core Concepts
    • EAA, EN 301 549, WCAG 2.1 AA — How Do These Three Connect?
    • The Four Principles of WCAG 2.1 AA (POUR)
  • Practical Application
    • Example 1: Forms — The Most Common Source of Complaints
    • Example 2: Keyboard Navigation and Focus Management
    • Example 3: Semantic HTML and ARIA
    • Example 4: Color Contrast and Conveying Information Without Color
    • Example 5: Mobile Accessibility and Commonly Missed Areas
  • Implementation Cost and Benefits
    • Benefits
    • Drawbacks and Considerations
    • Most Common Mistakes in Practice
  • 15 Practical Checklist Items for EAA Compliance
  • Closing Thoughts
  • References

Core Concepts

EAA, EN 301 549, WCAG 2.1 AA — How Do These Three Connect?

The EAA (European Accessibility Act) is a digital accessibility mandate that applies across all 27 EU member states. It covers digital services broadly — websites, mobile apps, e-commerce, online banking, ticketing systems, and more. The technical standard it adopts is EN 301 549 v3.2.1, and the core of that standard is based on WCAG 2.1 Level AA. In other words, complying with WCAG 2.1 AA grants you a "presumption of conformity" with the EAA, making it the practical baseline we need to meet.

New digital services are subject to immediate compliance from June 28, 2025, and existing content may have varying transition schedules by country, but under EU terms a grace period applies until June 28, 2030. Importantly, even "existing content" becomes immediately subject to compliance the moment a new feature is added.

The Four Principles of WCAG 2.1 AA (POUR)

Understanding these four principles first will give you context for why the examples that follow are necessary. Each example will indicate which principles are involved, so you can use this as a reference frame.

Principle English Key Content
Perceivable Perceivable Text alternatives, captions, color contrast, image alt
Operable Operable Keyboard accessibility, sufficient time allowance, seizure prevention
Understandable Understandable Language settings, error identification and guidance
Robust Robust Compatibility with assistive technologies, valid markup

While these principles may seem independent, they overlap far more often in practice. A single form can simultaneously require Perceivable (error messages must be visible) + Understandable (what the error is must be explained) + Operable (corrections must be possible with keyboard only) + Robust (must be readable by screen readers).


Practical Application

Example 1: Forms — The Most Common Source of Complaints

Related principles: Perceivable + Understandable + Robust

Honestly, early on I didn't really understand what was wrong with using placeholder as a substitute for a label. Once you start typing, the placeholder disappears, and then the user has to rely on memory to recall what was supposed to go in that field. Screen reader users may have no way of knowing the purpose of the field at all.

html
<!-- ❌ Using only a placeholder -->
<input type="email" placeholder="Enter email" />
 
<!-- ✅ Providing full context with label + aria-describedby -->
<label for="email">Email address</label>
<input
  type="email"
  id="email"
  aria-describedby="email-hint"
  aria-required="true"
/>
<span id="email-hint">Please enter in the format example@domain.com</span>

aria-required="true" vs HTML5 required: They look similar but have differences. HTML5 required also triggers the browser's built-in validation (blocking empty values on submit), while aria-required="true" only communicates information to assistive technologies. If you use only aria-required="true" without the required attribute in custom validation logic, information is passed to assistive technologies but the browser's built-in validation will not function.

There is one thing to watch out for in error handling. Using an aria-live="polite" container together with a role="alert" span causes a duplicate announcement problem where the error message is read twice. This is because role="alert" inherently implies aria-live="assertive". It's best to choose one and use it consistently.

html
<!-- Method 1: aria-live="polite" — reads after the current task finishes (suitable for non-urgent guidance) -->
<div aria-live="polite" aria-atomic="true" class="sr-only" id="form-status">
  <!-- Insert message via JS -->
</div>
 
<!-- Method 2: role="alert" — interrupts and reads immediately, implies aria-live="assertive" (suitable for immediate error notifications) -->
<input
  type="email"
  id="email"
  aria-invalid="true"
  aria-describedby="email-error"
/>
<span id="email-error" role="alert">
  Invalid email format.
</span>

sr-only class: A pattern that hides content visually while keeping it readable by screen readers. If you're using Tailwind CSS, there is a built-in sr-only class; otherwise, you can add it manually.

css
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}
Attribute Role
aria-invalid="true" Communicates to assistive technologies that the input value is invalid
aria-live="polite" Reads after the current task completes — suitable for non-urgent messages
role="alert" Interrupts and reads immediately, equivalent to aria-live="assertive" — suitable for urgent error notifications

Example 2: Keyboard Navigation and Focus Management

Related principles: Operable

There are two most common mistakes in keyboard accessibility. Global outline: none and missing modal focus traps. The second one might initially seem like "why does trapping focus in a modal matter?" — but try navigating a modal with only the Tab key and you'll understand immediately.

css
/* ❌ Never do this */
* {
  outline: none;
}
 
/* ✅ Customize visually, but never remove */
*:focus-visible {
  outline: 3px solid #0066cc;
  outline-offset: 2px;
  border-radius: 4px;
}

:focus vs :focus-visible: :focus displays the focus ring even on mouse clicks, whereas :focus-visible only shows it during keyboard navigation. This is the currently recommended approach because it provides a clean UI for mouse users and clear focus indicators for keyboard users simultaneously. It can be used safely in modern browser environments that do not require IE11 support.

The following is example code for understanding how focus trapping works.

typescript
// Example for understanding focus trap mechanics (React)
function FocusTrap({ children, isActive }: { children: React.ReactNode; isActive: boolean }) {
  const containerRef = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    if (!isActive) return;
 
    const focusableSelectors = [
      'a[href]', 'button:not([disabled])', 'input:not([disabled])',
      'select:not([disabled])', 'textarea:not([disabled])',
      '[tabindex]:not([tabindex="-1"])'
    ].join(', ');
 
    const container = containerRef.current;
    if (!container) return;
 
    const focusableElements = container.querySelectorAll<HTMLElement>(focusableSelectors);
    const first = focusableElements[0];
    const last = focusableElements[focusableElements.length - 1];
 
    first?.focus();
 
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key !== 'Tab') return;
      if (e.shiftKey) {
        if (document.activeElement === first) {
          e.preventDefault();
          last?.focus();
        }
      } else {
        if (document.activeElement === last) {
          e.preventDefault();
          first?.focus();
        }
      }
    };
 
    container.addEventListener('keydown', handleKeyDown);
    return () => container.removeEventListener('keydown', handleKeyDown);
  }, [isActive]);
 
  return <div ref={containerRef}>{children}</div>;
}

Recommended libraries for production: The code above is for understanding the underlying mechanics. Edge cases such as Shadow DOM contexts, dynamically added focusable elements, and cross-browser activeElement differences come up quite frequently in real services. It is recommended to use well-tested libraries like focus-trap-react or @radix-ui/react-focus-scope that already handle these cases.


Example 3: Semantic HTML and ARIA

Related principles: Robust + Perceivable

You sometimes encounter markup in legacy codebases plastered with div and span. My first instinct in that situation was to pile on ARIA attributes to fix it, but looking back, the right order was to first clean up the parts that could be replaced with semantic HTML.

html
<!-- ❌ div overuse pattern -->
<div class="header">
  <div class="logo">My App</div>
  <div class="nav">
    <div onclick="goHome()">Home</div>
    <div onclick="goAbout()">About</div>
  </div>
</div>
<div class="main-content">
  <div class="title">Page title</div>
</div>
 
<!-- ✅ Semantic markup -->
<header>
  <a href="/" aria-label="Go to My App home">My App</a>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>
<main>
  <h1>Page title</h1>
</main>

The first rule of ARIA use is "use native HTML elements first." Officially named by W3C as the First Rule of ARIA Use, if you replace a <button> with a <div> bearing role="button", you must implement keyboard event handling, focus management, and Enter/Space key behavior yourself. You'd essentially be rebuilding from scratch everything the browser provides natively for the native element.

html
<!-- ❌ Adding role to a div -->
<div role="button" tabindex="0" onclick="submit()" onkeydown="handleKey(event)">
  Submit
</div>
 
<!-- ✅ Just use a button -->
<button type="submit">Submit</button>

Example 4: Color Contrast and Conveying Information Without Color

Related principles: Perceivable

Color contrast might seem like something designers should handle, but during development, when color palettes change or dark mode is added, contrast ratios often shift again. It's worth building the habit of checking regularly with DevTools or Lighthouse.

Content Type Minimum Contrast Ratio
Normal text 4.5:1 or higher
Large text (18pt+ or Bold 14pt+) 3:1 or higher
UI components and state indicators (button borders, focus rings, etc.) 3:1 or higher

Conveying information through color alone should also be avoided. If you only change the color — like "red items are errors" — users with color blindness will have difficulty determining whether there's an error.

html
<!-- ❌ Indicating errors with color only -->
<span style="color: red;">Email</span>
 
<!-- ✅ Combining icon + text -->
<span>
  <span aria-hidden="true">⚠️</span>
  Email <span class="error-label">(Input error)</span>
</span>

Example 5: Mobile Accessibility and Commonly Missed Areas

Related principles: Perceivable + Operable

Mobile accessibility is especially easy to miss compared to desktop. Two things in particular are frequently overlooked, and the faster the pace of work, the easier they are to skip.

Touch target size: The minimum touch target required by WCAG 2.5.5 (AAA) and WCAG 2.2's 2.5.8 (AA) is 44×44px. For users with tremors or who have difficulty with fine motor control, buttons that are too small are a significant barrier.

css
.touch-target {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

prefers-reduced-motion: Excessive animation can cause physical discomfort for users with vestibular disorders or photosensitive epilepsy. A single CSS media query is all you need to address this.

css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Image alt text is also an area that's surprisingly often missed. I frequently encounter code that uses "Image" or the filename as-is.

html
<!-- ❌ Meaningless alt text -->
<img src="chart.png" alt="Image" />
<img src="photo.jpg" alt="photo.jpg" />
 
<!-- ✅ Alt text that describes the context -->
<img
  src="chart.png"
  alt="Monthly revenue trend for Q1 2025. March increased 23% from the previous month."
/>
 
<!-- ✅ Decorative images use empty alt so screen readers skip them -->
<img src="divider.png" alt="" role="presentation" />

To summarize the criteria for alt text: describe the information conveyed by meaningful images specifically, set decorative images to alt="", and for complex charts or diagrams, use aria-describedby to link to a data table or detailed description.


Implementation Cost and Benefits

Benefits

Item Details
Market Expansion Approximately 101 million people within the EU have some form of disability, meaning accessibility improvements translate directly into a larger user base
Overall UX Improvement Keyboard navigation, clear content structure, and consistent UI improve the experience for all users, not just those with disabilities
SEO Benefits Accessibility improvements such as semantic markup and alternative text also positively impact search engine indexing
Reduced Technical Debt Component-level fixes propagate throughout the entire app, reducing the cost of repeated patches

Drawbacks and Considerations

Item Details Mitigation
Legacy Code Refactoring Transitioning existing div-based UIs to semantic structures requires significant effort Apply to new components first and gradually migrate legacy code
Automation Tool Limitations Tools like axe-core and Lighthouse can only detect 30–40% of all issues Automated scanning + periodic manual audits are both essential
Design-Development Collaboration Costs Color contrast, focus states, touch target sizes, and more must be considered at the design stage, increasing initial time Include accessibility specifications at the design stage using tools like the Figma A11y Annotation Kit
Ongoing Monitoring Burden Accessibility regression testing required with every new feature addition and release Integrate jest-axe and Pa11y into the CI/CD pipeline
Legal Risk Enforcement methods and penalty levels vary by country; some countries may impose significant fines or market exclusion Demonstrate sincere commitment to compliance through early auditing and writing an accessibility statement

Accessibility Statement: A document describing the accessibility status of your service, non-compliant items, improvement plans, and contact information. The EAA requires this statement, and even if full compliance is difficult, it plays an important role in formally expressing your commitment to sincere implementation. You can create one using the official accessibility statement generator from W3C WAI.

Most Common Mistakes in Practice

  1. Mistaking a Lighthouse score of 100 for perfect accessibility — Automation tools only catch 30–40% of all issues. Things like meaningful alt text, logical reading order, and error recovery experiences cannot be judged by tools. Testing by navigating directly with NVDA (Windows) or VoiceOver (macOS/iOS) must be done in parallel.

  2. Applying outline: none globally — Removing focus rings for aesthetic reasons is devastating for keyboard users. Rather than removing them, it's far better to customize them into a visually improved form using :focus-visible.

  3. Forgetting to re-audit accessibility when deploying new features — Even if existing content has a grace period, new features are subject to immediate compliance. Setting up a pipeline that automatically catches accessibility regressions in CI every time a new component is deployed can prevent this problem.


15 Practical Checklist Items for EAA Compliance

Ready to paste directly into your issue tracker or PR checklist.

Forms and Input Elements

  • 1. Connect <label> or aria-label to all input fields
  • 2. Convey error messages to screen readers via role="alert" or aria-live regions (be careful not to use both simultaneously)
  • 3. Explicitly mark required input fields with aria-required="true" or required

Keyboard Navigation

  • 4. Check for and remove global outline: none application
  • 5. Implement focus traps in modals and dropdowns, restoring focus to the original position on close
  • 6. Support closing overlays/dropdowns with the Escape key

Semantic Markup

  • 7. Apply landmark elements such as <header>, <nav>, <main>, <footer>
  • 8. One <h1> per page, maintaining a logical heading hierarchy (h1 → h2 → h3)
  • 9. Clearly distinguish <button> vs <a> roles (action=button, navigation=a)
  • 10. Verify the <html lang="en"> language declaration

Visual Accessibility

  • 11. Verify normal text color contrast ratio of at least 4.5:1
  • 12. Convey information through icons and text in addition to color
  • 13. Image alt text (specific descriptions for meaningful images, alt="" for decorative images)

Mobile and Motion

  • 14. Verify minimum touch target size of 44×44px
  • 15. Apply prefers-reduced-motion media query

Closing Thoughts

Trying to fix all 15 items at once from the beginning makes it hard to even get started. It's enough to check them one by one, starting with what you can do today. The task the EAA has left us is not "perfect accessibility," but building the habit of considering accessibility from the earliest stages of design.

3 steps you can start right now:

  1. Assess the current state with automated scanning — Open the Lighthouse tab in Chrome DevTools and run the Accessibility section. If you prefer the CLI, you can run a scan with npx @axe-core/cli <URL> (note that the old axe-cli is deprecated, so switch to @axe-core/cli). Identifying how many issues currently exist is the starting point.

  2. Integrate axe into component-level tests — Add jest-axe or vitest-axe to your existing test files and include the expect(await axe(container)).toHaveNoViolations() assertion in core components like Button, Modal, and Form. This automatically catches regressions on every deployment.

  3. Test core flows with keyboard only — Set the mouse aside and try to complete core flows like sign-up or purchase from start to finish using only the Tab/Shift+Tab/Enter/Space/Escape keys. The points where you get stuck are your highest-priority fixes.


References

  • EAA Enforcement in Europe Following the June 2025 Deadline | Pivotal Accessibility
  • Meeting European Accessibility Act (EAA) Standards: A Developer's Checklist — SitePoint
  • European Accessibility Act (EAA): Complete Compliance Guide 2025 - AllAccessible
  • A Developer's Guide to European Accessibility Act 2025 - Chromatic
  • European Accessibility Act (EAA) 2025: Full Compliance Checklist - Heurilens
  • Web Accessibility 2025: EAA Compliance & Modern Testing Tools
  • EAA Compliance Checklist - Usercentrics
  • European Accessibility Act (EAA) - European Commission (Official)
  • Web Accessibility Evaluation Tools List | W3C WAI
  • axe-core GitHub Repository (Deque Systems)
  • Community Article - Introduction to the European Accessibility Act (EAA) - Nuli (Naver)
#웹접근성#WCAG2.1AA#EAA#ARIA#시맨틱HTML#React#키보드내비게이션#axe-core#TypeScript#jest-axe
Share

Table of Contents

Table of ContentsCore ConceptsEAA, EN 301 549, WCAG 2.1 AA — How Do These Three Connect?The Four Principles of WCAG 2.1 AA (POUR)Practical ApplicationExample 1: Forms — The Most Common Source of ComplaintsExample 2: Keyboard Navigation and Focus ManagementExample 3: Semantic HTML and ARIAExample 4: Color Contrast and Conveying Information Without ColorExample 5: Mobile Accessibility and Commonly Missed AreasImplementation Cost and BenefitsBenefitsDrawbacks and ConsiderationsMost Common Mistakes in Practice15 Practical Checklist Items for EAA ComplianceClosing ThoughtsReferences

Recommended Posts

Removing Popper.js with CSS Anchor Positioning — Floating UI Handled Directly by the Browser
frontend

Removing Popper.js with CSS Anchor Positioning — Floating UI Handled Directly by the Browser

Anyone who has done frontend work eventually thinks to themselves: "Why is attaching a dropdown below a button this complicated?" When I first built my team's d...

June 26, 202621 min read
Vite 8 + Rolldown: How a Dual-Bundler Integration Cut Production Build Times by 87%
frontend

Vite 8 + Rolldown: How a Dual-Bundler Integration Cut Production Build Times by 87%

(Vite 8 Rolldown migration | build speed) When I first heard that a 46-second build had dropped to 6 seconds, I was honestly skeptical. Benchmark numbers alw...

June 26, 202617 min read
From Direct DB Calls Without RSC to Streaming — Where TanStack Start and Next.js Diverge on Full-Stack Boundaries
frontend

From Direct DB Calls Without RSC to Streaming — Where TanStack Start and Next.js Diverge on Full-Stack Boundaries

When Next.js 13 App Router launched, I'll be honest — it threw me off. The anxiety of toggling on and off wondering "is it just me?", the runtime errors from t...

June 26, 202627 min read
Implementing Cloudflare Workers for Edge MFE Orchestration to Reduce TTFB
frontend

Implementing Cloudflare Workers for Edge MFE Orchestration to Reduce TTFB

Honestly, when I first heard about micro-frontends (MFE), my immediate thought was, "Independent deployments per team? Sounds great — but doesn't that mean the ...

June 23, 202623 min read
TanStack DB in Practice: How a Client-Side DB Changes Optimistic Updates
frontend

TanStack DB in Practice: How a Client-Side DB Changes Optimistic Updates

Honestly, when I first heard "client-side embedded DB," my reaction was "just another buzzword." TanStack Query already handles server state management well — w...

June 23, 202617 min read
Tailwind v4 @container — Responsive Components That Hold Up in Sidebars and Grids Alike
frontend

Tailwind v4 @container — Responsive Components That Hold Up in Sidebars and Grids Alike

At some point in responsive UI work, you hit a wall. A card component you carefully crafted works perfectly in the main feed, but the moment you drop the same c...

June 23, 202618 min read