React Accessibility Guide 2026: WCAG Compliance for React & Next.js Apps
React's component model makes accessibility both easier and harder than vanilla HTML. Easier because you can encode accessible patterns once and reuse them everywhere. Harder because the abstraction layer hides semantic structure from developers who aren't thinking about it — and single-page app routing breaks browser accessibility behaviors that users rely on.
Quick Summary
- React doesn't generate inaccessible HTML by default — but many common patterns do
- Client-side routing (React Router, Next.js App Router) requires explicit focus management
- axe-core catches ~30–40% of WCAG issues automatically; manual screen reader testing is still required
- jest-axe and @axe-core/playwright integrate accessibility checks into your existing test suite
- Headless UI libraries (Radix, Headless UI, shadcn/ui) handle ARIA patterns correctly — roll-your-own components often don't
The Most Common React Accessibility Mistakes
Most React accessibility issues fall into a small set of repeating patterns. The good news: fixing them in your component library fixes them everywhere.
onClick on non-interactive elements
Attaching onClick to a <div> or <span> makes it mouse-clickable but not keyboard-focusable or screen reader-accessible. Use <button> for actions, <a href> for navigation. If you must use a div, add role='button', tabIndex={0}, and onKeyDown handlers — but a <button> is almost always correct.
Missing focus management after route changes
When React Router or Next.js navigates between pages, focus stays wherever it was. Screen reader users never know a new page loaded. In Next.js App Router, use the built-in scroll-to-top focus behavior. In React Router, manually move focus to the page heading or a skip target after navigation.
Modal and dialog focus traps
Opening a modal without trapping keyboard focus lets users tab behind it, where they can interact with obscured content. When a modal opens: move focus inside it, trap Tab/Shift+Tab within it, and return focus to the trigger element when it closes. Libraries like Radix Dialog and Headless UI Dialog handle this correctly.
Dynamic content without ARIA live regions
React's reactive updates don't announce changes to screen readers. Loading states, success toasts, form validation errors, and live search results need aria-live='polite' (or 'assertive' for urgent alerts) on a region element to be announced.
Images without alt text
Next.js <Image> and React <img> don't require alt text — you have to add it. For decorative images, use alt='' (empty string). For informative images, describe the content. For images inside <button> or <a>, the alt text becomes the control's accessible name.
Custom select/dropdown components
Building a styled select from scratch with onClick handlers almost always produces an inaccessible result. Native <select> is accessible by default. For custom styled dropdowns, implement the Combobox or Listbox ARIA patterns — or use Radix Select/Combobox which implements them for you.
Color contrast in Tailwind dark themes
Tailwind's slate/gray palette is beautiful but tricky. slate-400 text on slate-900 background passes. slate-500 on slate-800 is borderline. Always verify contrast ratios — don't assume dark themes are accessible by default.
Automated Testing with axe-core
axe-core is the open-source accessibility rules engine behind Deque Axe DevTools, the Chrome axe extension, and most accessibility testing tools. Integrating it into your test suite catches violations at commit time — before they reach production.
jest-axe (Unit / Component Tests)
Install
npm install --save-dev jest-axe @testing-library/react
Usage
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('Button is accessible', async () => {
const { container } = render(<MyButton label="Submit" />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});Catches ~30–40% of WCAG 2.1 AA issues. Best for component-level regression testing.
@axe-core/playwright (End-to-End Tests)
Install
npm install --save-dev @axe-core/playwright
Usage
import { checkA11y } from '@axe-core/playwright';
test('Homepage passes axe', async ({ page }) => {
await page.goto('/');
await checkA11y(page, undefined, {
detailedReport: true,
detailedReportOptions: { html: true },
});
});Runs against fully rendered pages including dynamic content. Better coverage than component tests alone.
Accessible Component Patterns
The right component library choice determines whether accessibility is easy or a constant battle. Here's how major options compare:
Radix UI / shadcn/ui
Radix implements WAI-ARIA design patterns correctly for every component: Dialog, Select, Tabs, Tooltip, Menu, and more. shadcn/ui wraps Radix with Tailwind styling. If you're starting a new project, this is the right choice.
Headless UI (Tailwind Labs)
Accessible, unstyled components for Dialog, Listbox, Combobox, Menu, Tabs, and Disclosure. Integrates naturally with Tailwind CSS. Fewer primitives than Radix but excellent quality on the components it covers.
React Aria (Adobe)
The most thorough accessibility implementation available, including internationalization and cross-platform behavior. Steeper learning curve and more boilerplate. Worth it for products with strict accessibility requirements (government, healthcare, enterprise).
Material UI (MUI)
Generally accessible but has known gaps in complex components (DataGrid, Date Pickers). Test thoroughly. Accessibility varies by component and version — don't assume coverage.
Ant Design
Accessibility has improved significantly in v5 but complex components (Table, Select, Tree) still have keyboard and ARIA issues. Requires custom testing and patching for WCAG AA compliance.
Custom components (no library)
Rolling your own dropdowns, modals, tabs, and tooltips almost always produces accessibility issues. ARIA design patterns are complex — each pattern has keyboard interaction requirements, focus management rules, and state announcements that are easy to miss.
Next.js-Specific Accessibility Notes
Next.js has built-in accessibility features, but several require explicit opt-in or configuration:
App Router focus management
Next.js App Router automatically announces route changes to screen readers and scrolls to the top on navigation — an improvement over Pages Router. Verify it works with your layout structure; nested layouts that don't reset scroll can interfere.
next/link and focus
Next.js <Link> renders an <a> tag — no accessibility issues. But programmatic navigation via router.push() requires manual focus management. Move focus to the page heading or a designated focus target after programmatic navigation.
eslint-plugin-jsx-a11y
Next.js 11+ ships with eslint-plugin-jsx-a11y configured by default. It catches missing alt text, invalid ARIA attributes, and non-interactive element click handlers at lint time. Check your .eslintrc to ensure it's enabled — it's off in some custom configurations.
next/image alt text
next/image requires an alt prop but accepts alt='' (empty string) for decorative images. The TypeScript types don't enforce meaningful alt text — add lint rules or code review processes to catch placeholder or missing descriptions.
Dynamic imports and lazy loading
Components loaded with next/dynamic can cause accessibility issues if they introduce interactive elements without focus management. Ensure dynamically loaded modals, drawers, and tooltips manage focus correctly on mount.
CI/CD Integration
Automated accessibility checks in your CI pipeline prevent regressions from reaching production. A GitHub Actions workflow that runs axe on every PR costs nothing and catches ~30% of WCAG violations before review.
GitHub Actions — axe-core via Playwright
name: Accessibility Tests
on: [pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run build
- run: npx playwright install --with-deps chromium
- run: npx playwright test --project=accessibilityThe axe-core library catches violations with zero false positives — any failure it reports is a real accessibility issue. It does not catch everything: color contrast issues in dynamic states, focus order problems, and screen reader announcement quality all require manual testing. Use automated CI coverage as a floor, not a ceiling.
Manual Screen Reader Testing for React Apps
NVDA + Chrome (Windows)
Most common combination used by screen reader users in the US. Test keyboard navigation, modal behavior, form validation, and live region announcements.
VoiceOver + Safari (macOS/iOS)
Built into macOS and iOS. Important for Apple ecosystem users. VoiceOver on iOS with Safari is the most-used screen reader on mobile.
JAWS + Chrome (Windows)
Most common in enterprise and government contexts. Required for Section 508 compliance documentation. More expensive but required for federal contract work.
Ongoing Monitoring for React Apps
Automated testing in CI catches issues before deployment. Ongoing monitoring catches regressions in production — from content updates, A/B tests, or third-party script changes. React SPAs are particularly susceptible because route-level changes don't trigger full page reloads that some monitoring tools depend on.
RatedWithAI
Automated WCAG scanning with continuous monitoring, single-page app support, exportable reports. Free scan available.
Deque Axe Monitor
Enterprise-grade axe-core-powered monitoring. Integrates with Jira, GitHub, and Azure DevOps for issue tracking workflows.
Siteimprove
Full accessibility, quality, and analytics platform. Strong on enterprise workflows and prioritization dashboards.
Pope Tech
axe-core powered, strong team collaboration features. Popular in higher education and government agencies.
Scan Your React App for Free
RatedWithAI scans against WCAG 2.1 AA criteria and generates exportable reports for your React or Next.js app. No signup required for the initial scan.
Sponsored
Find accessibility issues at the component level
Deque Axe DevTools Pro integrates with your React dev environment — catch issues in the browser as you build, before they hit your test suite.