AIWebPageSEO Accessibility Fixes Fix Accessibility in React / Next.js (ARIA, Focus, SSR)

How to Fix Accessibility in React / Next.js (ARIA, Focus, SSR)

React and Next.js apps fail accessibility audits in distinctive ways: client-side route changes that don't announce, headless component libraries with incorrect ARIA, focus that disappears after navigation, and JSX that wraps everything in divs. This guide covers the React/Next.js audit-and-fix workflow. Pair with our accessibility guide and the Shopify version.

Step-by-step: How to fix accessibility in React / Next.js

  1. Integrate axe-core in development. Install @axe-core/react (CRA, Vite) or @axe-core/next (Next.js plugin). Logs accessibility errors to dev console on every render. Catches 30-40% of WCAG issues automatically — never substitutes for manual testing.
  2. Add jest-axe to your test suite. jest-axe runs axe-core against rendered components in unit tests. Wraps render(<Component/>) → expect(await axe(container)).toHaveNoViolations(). Gates accessibility regressions in CI.
  3. Fix client-route focus. In Next.js App Router (since v13), route changes don't move focus by default. Screen reader users lose context. Solution: useEffect on pathname change → focus the main h1 with tabindex='-1'. Or use next/link's focus management patches.
  4. Replace div soup with semantic JSX. Audit JSX: every <div> that's clickable should be <button>; every navigation cluster should be <nav>; lists should be <ul>/<ol>; sections should be <section> or <article>. Forms need <label> with htmlFor.
  5. Audit headless component library ARIA. Radix UI, Headless UI, React Aria, shadcn/ui — generally accessible. But: custom implementations of dialog, dropdown, listbox often miss aria-expanded, aria-controls, focus trap. Test each interactive component with keyboard only.
  6. Fix dynamic content announcements. When content changes without route change (e.g., filter results update), screen readers don't notice. Wrap dynamic regions in role='status' or aria-live='polite' so updates are announced.
  7. Run Lighthouse and WAVE. Lighthouse a11y audit on representative URLs. WAVE browser extension on rendered DOM. Both miss things axe doesn't — running multiple tools catches more.
Tip. Pin your framework, dependency, and config versions in a single internal doc (Next.js version, React version, rendering strategy choices, custom config). When something breaks after a framework or library update, you have a baseline to compare against.

🔎 Audit React accessibility

Find ARIA, focus and semantic issues in your React app.

Run Accessibility Audit →

Frequently Asked Questions

Does Next.js handle accessibility automatically?

Partially. Next.js provides next/image with required alt prop (warns if missing), next/link with proper anchor element, ESLint plugin with a11y rules (jsx-a11y). But: doesn't manage route-change focus, doesn't validate ARIA correctness, doesn't enforce semantic JSX. You still need axe-core and manual testing.

Why do React apps fail focus management on route change?

SPAs don't reload the page on navigation — the URL changes via History API but the DOM persists. Screen readers track focus, not URL. Without explicit focus management on route change, focus stays on whatever the user clicked (often invisible after content updates). Solution: move focus to the new page's heading or main element after route change.

Best accessibility libraries for React?

Radix UI (primitives — most flexible, fully accessible). React Aria (Adobe's library — most thorough). Headless UI (Tailwind team — simpler, focused). shadcn/ui (built on Radix — copy-paste components). Most projects use Radix or Headless UI as the foundation.

How do I test React keyboard navigation in tests?

React Testing Library's userEvent.tab() simulates Tab key. Combine with toHaveFocus() assertions. Test that Tab order matches visual order, focus is visible on every element, modal dialogs trap focus, Escape closes overlays. jest-axe catches static ARIA issues; userEvent catches behaviour.

What does the jsx-a11y ESLint plugin catch?

Static analysis of JSX accessibility issues: missing alt on <img>, missing labels on form inputs, invalid ARIA attributes, click handlers on non-interactive elements (<div onClick=...>), missing keyboard handlers on click handlers. Catches 20-30% of issues at lint time. eslint-plugin-jsx-a11y is included in create-next-app and Next.js defaults.

Got a problem?