intermediate 3 min answer Multiple choice

A single-page application replaces the page content when the route changes. Sighted users see a new page. A screen-reader user hears nothing, and their keyboard focus is now at the top of the document. Which change fixes this at the right level?

accessibilityspafocus-managementlive-regionsrouting
Pick one
Show the full answer Hide the answer

Second by second, what happens

The user activates a link. The router swaps the view, the URL changes, and the browser does none of the things it does for a real navigation: no document load, no reset of the accessibility tree, no announcement of a new page title, no focus moved to the top of the new document. The element that had focus is removed from the DOM, so focus falls back to document.body, which means the next Tab starts from the very beginning of the page — past the skip link, the logo and the whole navigation, every single time.

Nothing errors and no automated check fails, because every rule about the content is satisfied. The defect is in the transition, and transitions are not what automated scanners inspect. That is the general limit of tooling here: automated checks are usually credited with catching around a third of real barriers, because the rest are behaviours over time rather than properties of a tree.

The cost to the user is concrete rather than aesthetic. On an application with 30 navigation links, every route change means 30 or more keystrokes to get back to the content, so a five-step task becomes a hundred-key ordeal, and the user cannot tell whether the page changed at all.

Why the fix belongs to the router

Because the router is the only component that knows a navigation happened. Putting focus management in each page means 40 implementations, of which 6 will be missing at any moment and the new one added next week will be the seventh. The architectural rule is that a behaviour required on every navigation is owned by the thing that navigates.

The implementation is three parts, none of them large:

  1. On route change, move focus to the new view's top-level heading, made programmatically focusable. The screen reader then reads the heading, which tells the user where they are, and the next Tab continues from the right place.
  2. Announce the destination in a polite live region that the router owns and updates, for the users whose setting does not read focus changes aloud.
  3. Set the document title so the browser and assistive technology both report the correct page, which also fixes history and tab labels for everyone.

Why the other options fail

  • aria-live on the application shell. A live region wrapping content that changes wholesale announces the entire new page, which is a torrent, and it does nothing about focus, so the Tab problem remains. Live regions are for small, targeted updates — a toast, a validation message, a result count.
  • role="main" per page. A correct landmark and a real improvement for navigating a static page. It does not announce anything on a route change and does not move focus, so the reported symptoms are unchanged.
  • Waiting for the screen reader to notice. Screen readers do not re-read a document because its subtree changed; that is why the ARIA live-region mechanism exists at all.

What would have to be true for the automated checks to have caught it

They would have to assert on behaviour over time rather than on a static tree: navigate, then check that focus is inside the new view and that an announcement was made. That is a test you can write — the most valuable accessibility test most applications lack — and it belongs in the same suite as routing tests, because it is a routing behaviour. It costs a few hours once, and it is cheaper than the audit finding it replaces: since WCAG 2.2 became a W3C Recommendation in 2023 and conformance became a procurement question in several markets, this class of defect is found by someone outside the team, at a worse moment.

When this is the wrong answer

If a route change replaces only part of the page — a tab panel, a filtered list — do not move focus to a heading, because yanking focus away from the control the user just operated is worse than saying nothing. There, announce the result in a live region and leave focus where it is. The rule is the general one for focus: move it when the user's context has genuinely changed, and never move it as a side effect of something they did not initiate.