React · SPA · Development

React & SPA Accessibility: The WCAG Bugs Hiding After Page Load

  • React
  • SPA
  • Development

Your Lighthouse score says 100. Your accessibility scanner is green across the board. And the moment a real screen reader user clicks a link inside your React app, the experience goes completely, silently dark.

Here's the uncomfortable truth about single-page applications: automated tools test the page that loads once. They were never built to watch what happens after — the client-side route changes, the modals, the live-updating lists that make up 95% of how someone actually uses your product.

Picture This Scenario

A product manager at a mid-size SaaS company gets word that a prospective enterprise customer's procurement team flagged accessibility gaps during a vendor security review. The dev team is confused — they ran axe last quarter, fixed every issue it found, and Lighthouse gives the app a clean bill of health.

So someone on the team actually turns on a screen reader and tries to use the app themselves. They click into a dashboard. Nothing is announced. They open a settings modal. Focus stays exactly where it was, and they can tab straight through the "hidden" background content as if the modal doesn't exist. They filter a table. The results change on screen, but nothing is said out loud.

None of that shows up in a scanner. All of it is a real, everyday experience for a screen reader user navigating a typical React app.

Why Multi-Page Sites Got This For Free

Traditional websites have a built-in accessibility safety net that most teams never think about. Every time the browser loads a new page, it does two things automatically: it announces the new page title to screen readers, and it moves focus to the top of the document.

Client-side routing throws that safety net away. When React Router or a similar library swaps out the contents of a <div>, the browser doesn't consider that a "navigation" at all — no new page load happened, so there's nothing for the browser or assistive technology to announce. Visually, everything updated. To a screen reader, nothing happened.

That gap is the single biggest structural accessibility problem in modern SPAs, and it's almost never caught by automated testing.

The Fix: Programmatic Focus Management

The standard remedy is to rebuild, in code, the behavior browsers used to give you for free. On every route change, your app needs to:

  • Move keyboard focus to the new view's main heading or main content container
  • Update document.title to reflect the new page/view
  • Do both of these reliably, on every route, not just the ones someone remembered to test

Skip this and a screen reader user is left in one of two bad states: focused on an element that no longer exists in the DOM, or silently sitting at the top of the page with zero indication that a "navigation" even occurred. Either way, they have no idea where they are — which, for someone who can't just glance at the screen, is disorienting in a way sighted developers rarely notice during their own testing.

Dynamic Updates Need Live Regions Too

Route changes aren't the only silent update in a typical SPA. A filtered product list re-rendering, an item added to a cart, an inline form validation error appearing under a field — all of these are dynamic content changes that need to be announced, not just displayed.

This is exactly what 4.1.3 Status Messages (Level AA) covers: status messages need to be programmatically determinable so assistive technology can present them to the user without stealing focus. In practice, that means ARIA live regions (aria-live="polite" or role="status"/role="alert" where appropriate) wired up around the parts of your UI that change without a full navigation.

It's an easy criterion to miss for a very human reason: the visual update alone is enough to satisfy a sighted developer clicking through their own feature. Nothing looks broken. But nothing was ever communicated to anyone using a screen reader either.

The Stat: WebAIM's annual "WebAIM Million" evaluation of the top 1,000,000 home pages has repeatedly found the vast majority — in recent years around 95-96% — have detectable WCAG 2 failures, and that's just what's visible on initial page load, before a single client-side interaction happens. (WebAIM: The WebAIM Million)

WCAG 2 failure rate across the WebAIM Million WebAIM Million: WCAG 2 Failure Rate True before a single client-side interaction happens Have WCAG failures 95.9% No failures detected 4.1% Source: WebAIM, "The WebAIM Million" annual accessibility evaluation

Shared Components Multiply Every Mistake

Here's what makes SPA accessibility debt especially dangerous: it scales with your component library, not with your page count.

A single shared <Modal> or <Dropdown> component with a missing focus trap or an incorrect ARIA role doesn't cause one bug — it causes that exact same bug everywhere the component is reused, often across dozens of screens built by different engineers who reasonably assumed the shared component was already accessible. This is the same dynamic we cover in our piece on building accessibility into your design system from day one: fix it once at the source, or debug it forever at every call site.

Modals deserve special attention here because they're both extremely common and extremely easy to get wrong. A properly accessible dialog needs:

  • Focus trapping — keyboard focus stays contained inside the open dialog and can't tab out to content "behind" it
  • Focus restoration — closing the dialog returns focus to whatever element triggered it in the first place
  • A visible open state that assistive tech agrees with — the dialog needs to actually receive focus when it opens, not just render visually

The most common SPA failure pattern here is a modal that appears on screen but never receives focus. A sighted keyboard user might not even notice; a screen reader user has no idea the modal opened at all and will keep tabbing through content that's supposed to be inaccessible while it's open.

Automated Tools Can't See Any Of This

Before vs. After: What Each Testing Method Actually Catches

Testing Method Catches Missing Alt Text / Contrast Catches Route-Change Focus Loss Catches Missing Live-Region Announcements Catches Real Keyboard Trap in Modal
Lighthouse / axe on initial load Yes No No Partial at best
CI-integrated automated scan Yes No No Partial at best
Manual keyboard-only walkthrough Partial Yes No Yes
Manual screen reader walkthrough of real user flows Partial Yes Yes Yes

Automated scanners catch a meaningful floor of static markup problems, and you should absolutely keep running them. But they are structurally incapable of catching route-change focus management, live-region announcements, or genuine keyboard operability of custom components — those are behavioral, interaction-based issues that only show up when a real person navigates your actual app with a keyboard and a screen reader.

If your last accessibility check was a scanner run against your homepage, you've tested the one part of your SPA that behaves the most like a normal website. Everything past that first click is unverified.

Disability Is Not a Niche Consideration

The World Health Organization estimates that over 1 billion people worldwide — roughly 16% of the global population — live with some form of disability (World Health Organization). In the U.S. specifically, the CDC estimates that roughly 1 in 4 adults live with some type of disability (CDC). For a product built to scale, that's not an edge case to defer to "phase two" — it's a substantial share of your total addressable market experiencing your app's post-load behavior every single day.

And the legal landscape around this keeps tightening: UsableNet's annual ADA Digital Accessibility Lawsuit Report has tracked several thousand federal ADA website lawsuits filed per year in recent years, with plaintiffs' firms increasingly sending pre-suit demand letters in addition to filing suit (UsableNet). Retail and ecommerce consistently rank among the most-sued industries in those same reports (UsableNet) — and SaaS dashboards, checkout flows, and account settings pages are exactly the kind of interaction-heavy, client-side-routed surfaces where the gaps we've described here tend to live.

Get Your SPA Actually Tested

A clean Lighthouse score on your marketing site tells you almost nothing about whether your logged-in React app is usable with a keyboard or a screen reader. The bugs that matter most — lost focus on route change, silent dynamic updates, modals that trap no one and restore nothing — only show up when someone actually drives the app the way your real users do.

That's exactly the gap we close. Get your React or SPA product audited for real accessibility, not just a Lighthouse score — our team manually walks your actual routes, modals, and dynamic components with a keyboard and screen reader, then hands you a prioritized, developer-ready fix list mapped to the specific WCAG success criteria involved.

Start your SPA accessibility audit and find out what your scanner has been missing.