Next.js is a React-based framework that powers a significant portion of modern web applications, offering server-side rendering, static site generation, and client-side navigation. While Next.js provides some built-in accessibility features like the next/image component with required alt text and eslint-plugin-jsx-a11y for development-time linting, the framework's single-page application nature introduces unique accessibility challenges. Client-side route transitions can leave screen reader users unaware that the page has changed. Dynamic content loading through React state updates may not be announced to assistive technology. The component-based architecture means that accessibility must be considered at every level, from individual UI components to page-level layout and navigation patterns. Many popular Next.js component libraries and UI kits do not fully meet WCAG standards, and developers often build custom interactive components like modals, dropdowns, and tabs without proper ARIA patterns. With the European Accessibility Act requiring digital accessibility compliance, Next.js developers must go beyond basic HTML semantics and implement robust focus management, route change announcements, and keyboard interaction patterns throughout their applications. This checklist addresses the most common accessibility pitfalls specific to Next.js and provides concrete code-level solutions.

Common Accessibility Issues

critical

Route Changes Not Announced to Screen Readers

WCAG 4.1.3

Next.js performs client-side navigation when using the Link component or router.push(), which updates the page content without a full page reload. Screen reader users are not informed that the page has changed because the browser does not trigger a page load event. The user may continue reading the previous page's content without realizing that new content has loaded.

How to fix:

Implement a route change announcement component that listens to Next.js router events and announces the new page title to screen readers. Use the routeChangeComplete event from next/router to trigger an ARIA live region update. Additionally, manage focus by moving it to the main content heading or the top of the page after navigation completes.

Before
// No route change handling
import Link from 'next/link';

export default function Nav() {
  return (
    <nav>
      <Link href="/about">About</Link>
      <Link href="/contact">Contact</Link>
    </nav>
  );
}
After
// RouteAnnouncer component
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';

export function RouteAnnouncer() {
  const router = useRouter();
  const [announcement, setAnnouncement] = useState('');
  const mainRef = useRef(null);

  useEffect(() => {
    const handleRouteChange = () => {
      const title = document.title;
      setAnnouncement(`Navigated to ${title}`);
      const main = document.getElementById('main-content');
      if (main) main.focus();
    };
    router.events.on('routeChangeComplete', handleRouteChange);
    return () => router.events.off('routeChangeComplete', handleRouteChange);
  }, [router]);

  return (
    <div aria-live="assertive" aria-atomic="true"
      className="sr-only">{announcement}</div>
  );
}
critical

Missing Alt Text on next/image Components

WCAG 1.1.1

While Next.js's Image component requires an alt prop (it will throw a lint error if omitted), developers frequently pass empty strings or meaningless values to suppress the warning. Decorative images are sometimes given descriptive alt text when they should have empty alt, and informative images receive generic text like 'image' or 'photo'.

How to fix:

Establish a team convention for alt text: informative images must have descriptive alt text that conveys the image's purpose, and purely decorative images should use alt="" with the role='presentation' attribute. Enable eslint-plugin-jsx-a11y in your ESLint configuration to catch missing and empty alt text during development. Review all Image component usages across the codebase.

Before
import Image from 'next/image';

<Image src="/hero.jpg" alt="image" width={800} height={400} />
<Image src="/divider.svg" alt="decorative divider line" width={800} height={2} />
After
import Image from 'next/image';

<Image src="/hero.jpg" 
  alt="Customer support team assisting a client via video call" 
  width={800} height={400} />
<Image src="/divider.svg" 
  alt="" role="presentation" 
  width={800} height={2} />
critical

Interactive Components Without Keyboard Support

WCAG 2.1.1

Custom dropdown menus, modal dialogs, tabs, accordions, and other interactive components built in React often rely on onClick handlers attached to div or span elements, making them inoperable for keyboard users. These components may also lack proper ARIA roles, states, and properties, making them invisible or confusing to screen reader users.

How to fix:

Use semantic HTML elements (button for actions, a for navigation) as the foundation for interactive components. For complex widgets, follow the WAI-ARIA Authoring Practices patterns which define the expected keyboard interactions and ARIA attributes for each widget type. Consider using an accessible component library like Radix UI, React Aria, or Headless UI that implements these patterns correctly.

serious

Form Validation Errors Not Associated with Inputs

WCAG 3.3.1

Next.js applications frequently implement form validation using React state to display error messages near form fields. These error messages are typically rendered as separate div or span elements that are not programmatically associated with their corresponding input fields. Screen reader users may not be aware that an error has occurred or which field the error relates to.

How to fix:

Associate error messages with their form fields using aria-describedby, linking the input's aria-describedby attribute to the error message element's id. Set aria-invalid='true' on fields with errors. Use an aria-live region to announce errors when they appear. For form-level error summaries, move focus to the summary on submission and use role='alert'.

Before
<div className="form-field">
  <input type="email" 
    value={email} onChange={handleChange} />
  {errors.email && (
    <span className="error">{errors.email}</span>
  )}
</div>
After
<div className="form-field">
  <label htmlFor="email">Email address</label>
  <input type="email" id="email"
    value={email} onChange={handleChange}
    aria-invalid={errors.email ? 'true' : undefined}
    aria-describedby={errors.email ? 'email-error' : undefined} />
  {errors.email && (
    <span id="email-error" className="error" 
      role="alert">{errors.email}</span>
  )}
</div>
serious

Missing Page Titles and Metadata

WCAG 2.4.2

Next.js applications using the App Router or Pages Router may not set unique, descriptive page titles for every route. When using client-side navigation, the document title may remain unchanged as users move between pages, providing no orientation context for screen reader users who rely on the page title to understand where they are.

How to fix:

In the App Router, use the metadata export or generateMetadata function in each page.tsx to set unique titles. In the Pages Router, use next/head in every page component to set the title. Follow the pattern 'Page Name | Site Name' for consistency. Ensure titles are unique across all pages and accurately describe the page content.

serious

Dynamic Content Loaded Without Screen Reader Notification

WCAG 4.1.3

Next.js applications frequently load content dynamically through API calls, infinite scrolling, or React state updates. When new content appears on the page (search results, filtered product lists, notification messages), screen reader users are not informed because the DOM updates are silent to assistive technology by default.

How to fix:

Wrap dynamic content regions in elements with aria-live='polite' for non-urgent updates or aria-live='assertive' for critical notifications. For loading states, use aria-busy='true' on the container while content is loading and set it to 'false' when complete. Provide a status message that announces the result of the action, such as 'Showing 12 results for your search'.

Next.js-Specific Tips

  • Enable eslint-plugin-jsx-a11y in your Next.js project by adding it to your .eslintrc configuration. Next.js includes some accessibility rules by default through next/core-web-vitals, but the full jsx-a11y plugin provides over 30 additional rules that catch common accessibility issues at development time.
  • Use Next.js's built-in Image component (next/image) for all images, as it enforces the alt attribute requirement, automatically generates responsive srcset, and adds width/height to prevent layout shift that can disorient users with cognitive disabilities.
  • Implement a global focus management strategy by creating a reusable FocusManager component that restores focus to the correct element after route changes, modal dismissals, and dynamic content updates. Place this in your _app.tsx or root layout.
  • When using Next.js API routes to handle form submissions, always return structured error responses that your front-end can map to specific form fields with aria-describedby associations, rather than generic error messages that are disconnected from the form inputs.
  • Test your Next.js application with JavaScript disabled to verify that server-rendered content is accessible and semantic. Pages rendered via SSR or SSG should be fully readable and navigable before client-side hydration completes.

Recommended Tools

eslint-plugin-jsx-a11y

An ESLint plugin that provides static analysis of JSX to identify accessibility issues at development time, catching missing alt text, incorrect ARIA usage, missing form labels, and other WCAG violations before code reaches production.

React Aria by Adobe

A library of accessible React hooks that handle ARIA patterns, keyboard interactions, and focus management for common UI components like menus, dialogs, tabs, and form fields, built to WAI-ARIA specification.

axe-core/react

An accessibility testing library that logs WCAG violations directly to the browser console during development, running axe-core audits against your rendered React component output in real time.

Further Reading

Other CMS Checklists