Skip to content

React Router A11Y

DodaTech 4 min read

title: "React Router Accessibility — Focus on Navigate" weight: 17 description: "Learn how to manage focus during route transitions in React Router: focus on route change, announce page changes to screen readers, skip links for navigation, and handle focus in nested routes." date: 2026-06-28 lastmod: 2026-06-28 tags: [accessibility, react]


React Router does not automatically manage focus when routes change, requiring a focus management strategy that moves focus to the main content area on navigation and announces the new page to screen readers using live regions.

## What You'll Learn

You will implement focus management on route change, announce page transitions to screen readers, use skip links with React Router, and handle focus in nested route layouts.

## Why It Matters

When a user navigates to a new page in a single-page app, the focus remains on the link they clicked. Screen reader users do not know the page changed. The content may be scrolled off-screen. Focus must be moved intentionally.

## Real-World Use

A user clicks a product link in a SPA. The URL changes and new content renders, but focus stays on the link. The screen reader continues reading the old page. The user scrolls down and finds the new content, but does not know when the navigation completed.

## Router Focus Flow

```mermaid
flowchart TD
  A[Link Clicked] --> B[Route Changes]
  B --> C[New Content Renders]
  C --> D[Focus Main Content]
  D --> E[Announce New Page]
  E --> F[Continue Browsing]

Implementing Route Focus

Create a component that manages focus on route changes.

import { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';

function FocusOnNavigate({ children }) {
  const location = useLocation();
  const mainRef = useRef(null);

  useEffect(() => {
    if (mainRef.current) {
      mainRef.current.focus();
    }
    // Announce page change to screen readers
    const announcer = document.getElementById('route-announcer');
    if (announcer) {
      announcer.textContent = `Navigated to ${document.title}`;
    }
    window.scrollTo(0, 0);
  }, [location.pathname]);

  return (
    <main
      ref={mainRef}
      tabIndex={-1}
      id="main-content"
    >
      {children}
    </main>
  );
}
// Route announcer component
function RouteAnnouncer() {
  return (
    <div
      id="route-announcer"
      aria-live="polite"
      aria-atomic="true"
      style={{
        position: 'absolute',
        width: '1px',
        height: '1px',
        overflow: 'hidden',
        clip: 'rect(0, 0, 0, 0)'
      }}
    />
  );
}

// Skip link for React Router
function SkipLink() {
  return (
    <a
      href="#main-content"
      className="skip-link"
      onClick={(e) => {
        e.preventDefault();
        document.getElementById('main-content')?.focus();
      }}
    >
      Skip to main content
    </a>
  );
}
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #0056b3;
  color: white;
  padding: 8px 16px;
  z-index: 100;
}

.skip-link:focus {
  top: 0;
}
// App layout with route accessibility
function App() {
  const location = useLocation();

  useEffect(() => {
    document.title = getPageTitle(location.pathname);
  }, [location]);

  return (
    <>
      <RouteAnnouncer />
      <SkipLink />
      <Header />
      <FocusOnNavigate>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/products" element={<Products />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </FocusOnNavigate>
    </>
  );
}

Common Mistakes

  • Not managing focus on route changes
  • Moving focus to the wrong element (header instead of main content)
  • Not announcing page changes to screen readers
  • Forgetting scroll position reset on navigation
  • Breaking focus when routes share layouts
  • Not handling back/forward browser navigation
  • Making the main content area focusable without visual indication

Practice and Challenge

Practice 1: Add focus-on-navigate to a React Router app. Practice 2: Implement a route announcer for screen readers. Practice 3: Add a skip link that works with React Router. Practice 4: Test focus behavior with browser forward/back buttons. Practice 5: Handle focus in nested route layouts.

Challenge: Build a React Router app with 5 pages and a focus management system that: moves focus to the main content on every navigation, announces the page title to screen readers, resets scroll position, handles nested routes correctly, and includes a skip link that focuses the main content.

FAQ

Does React Router manage focus automatically?

No. React Router only changes the URL. You must implement focus management in a useEffect.

Where should focus go on route change?

Focus should go to the main content heading or the main content container.

How do I announce page changes?

Use a visually hidden div with aria-live='polite' and update its content on route change.

Should I reset scroll position?

Yes. Use window.scrollTo(0, 0) in the same useEffect that manages focus.

What about nested routes?

Focus should go to the deepest changed route content. Use useLocation to detect the specific change.

How do I handle skip links in React Router?

Use an anchor with href='#main-content' or a button that calls .focus() on the main element.

Mini Project

Create a React Router accessibility wrapper library with: FocusOnNavigate component for automatic focus management, RouteAnnouncer for screen reader announcements, SkipLink component, and ScrollReset behavior. The library should be a drop-in addition to any React Router app.

What's Next

Dynamic Content and Live Regions covers live regions for dynamic React content.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro