Monitoring Focus Order in Single-Page Apps: What Your Scanner Never Sees

November 10, 2026

Run an accessibility scanner against a single-page app and it will happily report on the page it loaded. Navigate to a product, open a filter drawer, close it, go back, and the scanner has nothing to say, because it never watched any of that happen. The focus order broke three navigations ago and nobody was looking.

Why the DOM the scanner saw is not the DOM the user got

Success Criterion 2.4.3, Focus Order, is Level A, and it says that if a page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability. On a server-rendered page, the DOM order mostly is the focus order, and a scanner can check it. On a single-page app, the DOM is a snapshot. Route changes re-render the main landmark, modals mount and unmount, and focus lands wherever the framework left it, which is often the top of the document or the element that used to be under the cursor.

The failure modes are specific and repeatable. Focus stays on a link that no longer exists after navigation, so the next Tab press drops the user somewhere unrelated. A modal closes and focus goes to the body instead of back to the button that opened it. A filter drawer opens and focus never enters it, so a keyboard user tabs through the page behind the drawer. A route change moves the visual content but leaves focus on the old heading, so a screen reader user hears nothing new. None of these show up in a scan, because the scan ran before any of them happened.

Monitoring what actually moves

The monitoring that catches this is not a scan, it is a scripted walkthrough. Drive the app with a real browser automation tool, perform the navigations a user performs, and assert focus position after each step: after route change, focus is on the new page heading or the main landmark with tabindex -1. After modal close, focus is back on the trigger. After drawer open, focus is inside the drawer and Tab stays inside it. These assertions belong in the same CI pipeline as the scanner, running against the same deploy, because they catch the class of failure the scanner structurally cannot.

The second layer is production monitoring of the thing users actually hit: focus loss events. Instrument the app to log when focus lands on the body element after an interaction, which is the signature of a focus target that disappeared. A spike in body-focus events after a deploy is a regression signal worth paging on, and it is a signal no scanner will ever give you. For the scanner side of the same gap, see When the Scan Says Zero Errors and the Checkout Is Still Blocked, and for the alert discipline that keeps these signals readable, Real Accessibility Monitoring: Alert vs Noise.

The fix is a pattern, not a patch

Every route change should move focus deliberately, every modal should trap focus and return it, and every drawer should do the same. Those are component patterns, not per-page fixes, and once they live in the shared components the monitoring assertions stop firing. The monitoring exists to prove the patterns hold after every deploy, not to catch the same bug on the same page every week.

Frequently asked questions

Why does our scanner pass a page whose focus order is broken?

Scanners read the DOM once at load time. Focus order breaks during navigation, modal open and close, and re-renders, all of which happen after the scan. The scanner structurally cannot see them.

What does WCAG 2.4.3 require for focus order?

Focusable components must receive focus in an order that preserves meaning and operability. On a single-page app that means focus has to be managed deliberately on every route change, not left wherever the framework happens to put it.

Where should focus go after a route change?

To the new page heading or the main landmark, with tabindex -1 so it can receive focus without entering the tab order. The screen reader should announce the new page, not silence.

How do we monitor focus problems in production?

Log focus landing on the body element after an interaction. That is the signature of a focus target that disappeared, and a spike after a deploy is a regression signal worth paging on.