<aside>
💡 A cross-cutting pattern for single-page applications. When the page doesn't fully reload, focus management becomes your responsibility — the browser won't do it for you.
</aside>
Why this matters
In a traditional multi-page site, the browser resets focus to the top of the page on navigation. In SPAs (React, Next.js, Angular, Vue), route changes swap content without a page load — focus stays wherever it was, or worse, lands on a now-invisible element. Screen reader users hear nothing change; keyboard users are stranded.
Route change pattern
What to do
- On route change, move focus to the new page's <h1> or <main> element
- Target element needs tabindex="-1" to receive programmatic focus (headings aren't focusable by default)
- Announce the new page title to screen readers via a live region or document.title update
- Don't focus skip-link on route change — it creates a jarring experience
Implementation
- React/Next.js: useEffect on route change → ref.current.focus() on the <h1>
- Vue Router: router.afterEach → nextTick → focus the heading
- Angular: subscribe to Router events → focus on NavigationEnd
- Fallback: use an aria-live="polite" visually-hidden region that announces "Navigated to [page title]"
Dynamic content insertion / removal
Content added
- If new content appears after the user's current focus position: do nothing (they'll encounter it naturally when tabbing forward)
- If new content appears before or replaces current focus: move focus to the new content, or announce it via live region