CodePark/ questions / Focus management
START PRACTICINGPRACTICE
Frontend question · Accessibility › Focus management

In a single-page app, what goes wrong for keyboard and screen reader users when the route changes? How do you fix it?

function PageHeading({ title }: { title: string }) {
  const ref = useRef<HTMLHeadingElement>(null);
  useEffect(() => {
    document.title = title + " | Shop";
    ref.current?.focus();
  }, [title]);
  return <h1 ref={ref} tabIndex={-1}>{title}</h1>;
}

In a normal website, clicking a link loads a new page. The browser resets focus to the top, and the screen reader announces the new page title. A single-page app (SPA) only swaps part of the DOM with JavaScript. So none of that happens.

What goes wrong:

  • Silence: the screen reader says nothing. The user doesn't know the page changed.
  • Lost focus: focus stays on the link that was clicked. If that link was removed, focus falls back to <body>. The next Tab starts from a strange place.
  • Stale title: document.title may not update, so the tab and history show the old page.

Fixes, usually together:

  • Update document.title on every route change.
  • Move focus to the new page's <h1>, or a wrapper around the main content, with tabindex="-1" so it can take focus from script. The screen reader reads the heading.
  • Or announce the change in a polite live region, such as "Order history, page loaded".

Some frameworks help. Next.js has a built-in route announcer. It reads document.title, then the <h1>, then the URL. But you still choose where focus goes. Test it with a screen reader, because teams prefer different patterns.

  • A client-side route change does not reset focus or announce the new page.
  • Focus can stay on a removed link and fall back to the body.
  • Update document.title on every route change.
  • Move focus to the new <h1> or main wrapper with tabindex="-1", or announce the change in a live region.
  • Frameworks like Next.js include a route announcer, but you still need to test.