If you build web apps with Next.js and sell software or subscriptions to customers in the EU, the EU Accessibility Act (Directive 2019/882) applies to you. Regulators in member states have started sending formal notices to companies shipping digital products without accessibility statements, and enterprise procurement teams are rejecting vendors who cannot verify compliance.
Here is what the standard requires, the specific Next.js and React component patterns that fail it, and how to write the compliance documentation you need.
Who is in scope
The law covers private-sector digital products and services sold to EU users:
- Ecommerce websites and apps selling physical or digital goods
- SaaS subscriptions with any consumer-facing component (signup, login, billing, support, marketing)
- Banking and financial services including fintech interfaces
- Transport services (ticketing and schedules)
- Telecommunications customer portals
- Audiovisual media and streaming
- E-books and e-reader apps
Companies with fewer than 10 employees and under 2 million EUR in annual turnover qualify for a microenterprise exemption from substantive requirements, but they still have to provide accessibility information on request. Pure B2B platforms technically sit outside the direct scope, but enterprise procurement teams routinely demand compliance during vendor onboarding.
What the law requires
The law is performance-based. It relies on EN 301 549, the European harmonized standard, which incorporates WCAG 2.1 Level AA for web content. If your Next.js application satisfies WCAG 2.1 Level AA, you meet the baseline requirement for web content.
Beyond the UI itself, you need:
- A public accessibility statement on your site
- A direct feedback mechanism for users to report accessibility barriers
- Testing records you can produce on request
A few common misconceptions to ignore:
- Overlay widgets do not comply. Accessibility overlay widgets that promise one-line script compliance do not meet EAA standards. Regulators and disability advocacy groups have explicitly rejected them.
- Level AAA is not required. Level AA is the benchmark.
- Third-party certification is optional. A documented self-assessment against WCAG 2.1 AA is legally acceptable.
10 Next.js patterns that fail WCAG 2.1 AA
Most Next.js apps built with Tailwind CSS and component libraries like shadcn/ui fail on a predictable set of criteria.
1. Color contrast (WCAG 1.4.3 and 1.4.11)
Normal text requires a 4.5:1 contrast ratio against its background. Large text and UI component borders need 3:1.
In Tailwind, text-gray-500 on bg-white is 4.6:1 (barely passing). text-gray-400 is 2.8:1, which fails. In shadcn/ui setups, text-muted-foreground is often borderline in light themes. Test every text token against every surface token where it renders.
2. Focus visibility (WCAG 2.4.7 and 1.4.11)
Keyboard focus indicators must be visible with at least a 3:1 contrast ratio against the background.
The default shadcn/ui focus ring class focus-visible:ring-ring/50 renders at 50% opacity. In most light themes, that drops contrast below 3:1. Replace it with full-opacity ring styles:
ring-2 ring-ring ring-offset-2 ring-offset-background Test focus states in both light and dark mode.
3. Keyboard accessibility (WCAG 2.1.1 and 2.1.2)
Every interactive control must be reachable and operable using only the keyboard.
Do not attach onClick handlers to <div> or <span> elements. Use <button>, <a>, or <Link>. For complex controls like modals, dropdowns, and comboboxes, use Radix UI primitives (which power shadcn/ui). They manage focus trapping and arrow-key navigation out of the box.
4. Form labels (WCAG 3.3.2)
Every input element needs a programmatically associated label.
Placeholders are not labels. Wrapping an input in a <div> with adjacent text does not link the label to the control. Use shadcn's <Label> component with htmlFor matching the input id. If you use react-hook-form with shadcn's <Form>, <FormField> and <FormLabel> wire this relationship automatically.
5. Heading hierarchy (WCAG 1.3.1 and 2.4.6)
Pages must have a single <h1> and avoid skipped levels.
A frequent issue in dashboard apps is jumping from an <h1> page title directly to <h3> widget titles because <h2> looked visually too large. Keep your heading tags sequential (h1 to h2 to h3). Adjust visual size with Tailwind typography utilities rather than swapping HTML heading tags.
6. Image alternative text (WCAG 1.1.1)
Informative images need descriptive alt text. Purely decorative images must have an empty alt attribute (alt="").
Avoid generic labels like alt="logo" (use the company name instead) or alt="hero illustration". Configure ESLint to flag missing alt attributes on next/image components.
7. Link purpose (WCAG 2.4.4)
Link text must make sense when read out of context by a screen reader.
Replace "Click here" or "Read more" with descriptive labels. Change "Read more" to "Read more about WCAG 2.2 AA", or change "Click here" to "View pricing plans".
8. Dynamic status messages (WCAG 4.1.3)
Async status changes (toast alerts, loading states, form submission errors) must be announced to assistive tech without stealing user focus.
Libraries like sonner include live region announcements. For inline form validation errors, connect the error container's id to the input using aria-describedby.
9. Mobile reflow (WCAG 1.4.10)
Content must fit a 320 CSS pixel viewport without requiring horizontal scrolling.
Avoid hardcoded layout widths like min-w-[800px]. Wrap data tables in containers with overflow-x-auto and switch layout rows to flex columns on small breakpoints (flex-col md:flex-row).
10. Touch target sizing (WCAG 2.5.8)
Interactive elements need a target area of at least 24x24 CSS pixels, with 44x44 recommended.
Small icon triggers often fail this requirement. If you need a visually compact indicator (such as an 8px pagination dot), place it inside an invisible 44px tap area:
<button className="flex items-center justify-center size-11" aria-label={`Go to slide ${i + 1}`}> <span className="size-2 rounded-full bg-primary" /> </button> Remediation benchmarks
Remediation effort scales with the number of unique templates and UI primitives:
| App size | Audit time | Remediation time | Total cost (in-house) | Total cost (consultant) |
|---|---|---|---|---|
| Marketing site (5-10 pages) | 4 hours | 8-16 hours | 1-2 dev days | $1,500-$3,000 |
| Small SaaS (15-30 screens) | 8 hours | 24-40 hours | 4-7 dev days | $5,000-$10,000 |
| Mid-size dashboard (40-80 screens) | 16 hours | 60-120 hours | 10-20 dev days | $15,000-$30,000 |
| Large platform (100+ screens) | 40 hours | 200+ hours | 30-50 dev days | $40,000+ |
Apps relying on standard headless primitives (like Radix) take significantly less time to remediate than apps using completely custom DOM trees for inputs and menus.
Publishing an accessibility statement
Publish your statement at /accessibility and link it in your footer. The statement should accurately describe your current status:
## Accessibility Statement for [Company Name] [Company Name] is committed to ensuring digital accessibility for people with disabilities. We are continually improving the user experience for everyone and applying the relevant accessibility standards. ### Conformance status [Product Name] is partially conformant with WCAG 2.1 Level AA. "Partially conformant" means that some parts of the content do not fully conform to the accessibility standard. [Or "fully conformant" if you've verified everything passes.] ### Compatibility [Product Name] is designed to be compatible with the following assistive technologies: - Latest versions of Chrome, Firefox, Safari, and Edge - VoiceOver on macOS and iOS - NVDA on Windows - TalkBack on Android ### Technical specifications [Product Name] relies on the following technologies for accessibility: - HTML - CSS - JavaScript - ARIA - Next.js, React, and Tailwind CSS ### Limitations and alternatives Despite our best efforts, some content may have minor accessibility limitations: - [Honest list, e.g.: "Some legacy charts do not include text alternatives. We are addressing this in our next release."] ### Feedback We welcome your feedback on the accessibility of [Product Name]. Please contact us if you encounter accessibility barriers: - Email: accessibility@yourcompany.com - Phone: [optional] - Postal address: [optional] We aim to respond to feedback within 5 business days. ### Assessment approach [Company Name] assessed the accessibility of [Product Name] by the following approaches: - Self-evaluation against WCAG 2.1 Level AA - Automated testing with axe-core - Manual testing with keyboard and screen reader - [Optional: third-party audit by [auditor name] on [date]] ### Date This statement was created on [date] using the [methodology]. Last reviewed: [date]. Practical rollout checklist
- Run automated axe-core scans across primary route groups (marketing, auth, main dashboard).
- Manually tab through critical funnels (checkout, signup, main feature flows) with the mouse unplugged.
- Publish
/accessibilityusing the markdown template above. An honest "partially conformant" declaration with known limitations is compliant under EAA rules, whereas having no statement is an immediate violation. - Route an alias like
accessibility@yourdomain.comto your engineering or support inbox to meet the mandatory feedback requirement. - If you receive an inquiry from an EU regulator, respond immediately. Most initial notices from member states (including Germany, France, the Netherlands, and Ireland) offer a 60- to 90-day corrective window to supply an accessibility statement and a remediation roadmap before statutory fines apply.
This is a shorter version of a guide I wrote at Frontkit Studio. The full post has the complete walkthrough.