All guides
Framework guide · Framework

Accessibility for Next.js apps.

Next.js inherits React's accessibility responsibility and adds a few framework-specific concerns: SSR hydration, App Router transitions, Image component, Metadata API for page titles. The patterns and audit workflow for Next.js 13/14/15.

Recurring issues

What goes wrong in Next.js projects.

01

Image without meaningful alt

The `next/image` Image component requires `alt` but does not enforce quality. Bulk-imported product catalogs often have `alt=""` or `alt="image"`. Use `alt=""` only for decorative images and meaningful text otherwise.

02

App Router route transitions without announcement

Client-side navigation does not announce the new page to screen readers. Use `useRouter()` from `next/navigation` + a custom announcer that says the new page title on `routeChangeComplete`.

03

Metadata API page titles missing

Pages without `export const metadata = { title: ... }` inherit the layout default. Every distinct page needs a unique, descriptive title for WCAG 2.4.2.

04

use client without aria-live for dynamic content

Components marked `"use client"` that render conditional alerts/toasts need to be inside an aria-live region or have `role="alert"` — same as plain React.

05

Suspense fallbacks not announced

Streaming SSR with Suspense shows loading skeletons that are silent to screen readers. Add `aria-busy="true"` to the suspended region or use `role="status"` on the fallback.

06

Server Components conditional rendering on async data

Async Server Components that show different UI based on database queries can produce inconsistent accessibility (different ARIA structures per state). Test the most common states explicitly.

Recommended tooling

The Next.js accessibility stack we recommend.

eslint-config-next (includes jsx-a11y)

npm i eslint-config-next

Default Next.js eslint config includes `eslint-plugin-jsx-a11y` rules. No extra install needed for basic React a11y linting.

@axe-core/react

npm i @axe-core/react

Runs axe in development. Add to a `"use client"` wrapper in your root layout, gated by `process.env.NODE_ENV === "development"`.

react-aria-components

npm i react-aria-components

Adobe-built accessible primitives, Server Component compatible (most components). Best ARIA correctness in the React ecosystem.

Headless UI for Next.js

npm i @headlessui/react

Tailwind-friendly accessible primitives. Mark consumers as `"use client"` since they need React state.

@axe-core/playwright

npm i @axe-core/playwright

For e2e accessibility tests in Playwright. Already shipped in many Next.js starters.

Audit workflow

Step-by-step for a Next.js accessibility audit.

  1. 1

    Use Metadata API for every page

    Each route segment defines `export const metadata = { title, description }`. Use `title.template` in root layout for "Page — Brand" pattern.

  2. 2

    Audit Image alt across the app

    grep for `<Image` and review every `alt=` value. Replace `alt=""` with meaningful text or confirm the image is decorative.

  3. 3

    Add route-change announcer

    In a client component in the root layout, listen to `usePathname()` changes and use an aria-live region to announce the new page title or route.

  4. 4

    Run @axe-core/react in dev

    Wrapper component that imports `@axe-core/react` in development only, mounts at app root.

  5. 5

    AccessProof scan on Vercel/Fly deployment

    Scan the deployed URL post-deploy. Catches issues that only appear at production (CDN caching, real network, hydration).

Scan your Next.js app

Run a WCAG audit on your Next.js site in 42 seconds.

External scan — no JS injected into your app

WCAG 2.2 + Section 508 + EN 301 549 in one pass

Court-ready PDF with element selectors

CI/CD gate — block deploys on regression

Works with Next.js on any hosting (Vercel, Netlify, Fly, self-hosted)

Free plan — 1 site, monthly scan

FAQ

Next.js-specific questions.

Is Server Components accessibility-friendly?

Server Components emit static HTML which is excellent for screen readers — content is in the DOM from first paint, no loading flicker. The trade-off: client-side state (toasts, form errors, dynamic announcements) lives in Client Components and needs explicit aria-live regions. Mix them carefully and test the boundary.

How do I announce route changes in App Router?

Create a client component near your root layout that uses `usePathname()` and `useEffect()` to update an aria-live region with the new route's title. Some libraries (e.g. `next-router-announcer`) do this for you. Pages Router has a similar pattern with `useRouter().events`.

Does Next.js Image hurt accessibility?

No — it forces you to provide alt (TypeScript errors if missing) and handles responsive sizing well. Its only accessibility hazard is the lazy-load placeholder, which is invisible until the image loads; screen readers wait for the eventual alt text. Use `priority` for above-the-fold images.

Should I use react-aria-components or headlessui in Next.js?

react-aria-components for maximum accessibility correctness, especially on complex widgets (combobox, listbox, color picker). Headless UI for ease of integration with Tailwind. Both are SSR-compatible. Many Next.js teams pick Headless UI for buttons/modals/menus and react-aria for the parts where headlessui is too thin.