Skip to content
Insights · Engineering

What accessibility actually requires in React

A perfect automated score does not mean an accessible application. Here is what the standard actually asks for, the part a tool can see, and the afternoon of manual checks that finds everything else.

Scroll
EngineeringSep 16, 20268 min readBy Salman Naqvi, Founder & CEO
What accessibility actually requires in React

Six things decide whether a React application works for somebody who is not using a mouse, and the automated score most teams rely on sees part of one of them. Whether the components you wrote say what they are. Whether the focus ring survived the design review. Where focus goes when the route changes and no page load happens. Whether inputs carry real labels rather than placeholders. Whether anything that changes without a navigation is announced. And whether the page works at double zoom with animation switched off. None is a plugin. Each is a decision inside a component you already own.

Two ordinary React habits make this a framework-shaped problem rather than a general web one. The first is building an interactive control out of a non-interactive element — a div with a click handler where a button belonged — which discards the keyboard behaviour, the role and the focusability the browser supplies for nothing. The second is replacing page loads with route changes, which removes the moment at which every browser and screen reader resets focus and announces a new page. Both are invisible to a developer with a mouse and working eyesight, and both are the default result of writing components the obvious way.

The number teams reach for first hides all of it. The HTTP Archive reports that "The median score for Lighthouse Accessibility audits rose to 84% over the past two years", then warns against reading it the way it is usually read: "Accessibility scores are an important tool, but people familiar with Goodhart's Law will know the danger of a measure becoming a target. We must also acknowledge that automated tests can only address a portion of the WCAG Success Criteria, and that a perfect score does not guarantee an accessible site" (HTTP Archive, Web Almanac 2024: Accessibility). The same chapter ranks front-end frameworks on that average, puts React at 87% against Stimulus at 91%, and concludes that "Stimulus, Remix and Qwik are several percent more accessible on average than React, Svelte or Ember.js." Four points is not the story. What the average cannot test is.

The standard itself is shorter and far more concrete than its reputation. On focus, Level AA requires that "Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible." On anything that changes on screen without a navigation, Level AA asks that "status messages can be programmatically determined through role or properties such that they can be presented to the user by assistive technologies without receiving focus" (W3C, Web Content Accessibility Guidelines (WCAG) 2.2). That is a floor with a legal shape rather than a proof that anyone can use the thing — and increasingly a procurement gate, since the Web Almanac notes that "the European Union (EU) mandates that by June 2025, websites in a wide range of sectors must adhere to the Web Content Accessibility Guidelines (WCAG) principles (via the EN 301 549 standard)".

The components you wrote have to say what they are, and ARIA is the expensive way to make them. MDN states the first rule of ARIA use as "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so", and names what the alternative obliges you to build: "if you choose to use ARIA, you are responsible for mimicking the equivalent browser behavior in script." Reporting WebAIM's survey of over one million home pages, the same page adds the finding that gives the rule teeth: "Home pages with ARIA present averaged 41% more detected errors than those without ARIA" (MDN Web Docs, ARIA).

The Almanac measures the exact habit that warning is aimed at: "We observed that over 50% of mobile sites had home pages with at least one element assigned the role="button" (up from 33% in 2022, and 29% in 2021 and 25% in 2020)", a rise it calls concerning because it suggests generic containers are being dressed as buttons. Another 18% of sites have a link carrying a button role, and a role "doesn't make the element function like its native counterpart" — links are not activated by the space key while buttons are. The React check takes a minute: count the click handlers not attached to a button or an anchor. Each is a control a keyboard cannot reach and a screen reader cannot name.

The focus ring is the cheapest item on this list and the most frequently deleted. "We discovered that 53% of websites apply :focus {outline: 0} (in 2022 it was 86%), which removes the default outline provided by browsers for focused interactive elements", the chapter reports, while only 12% use the focus-visible pseudo-class that shows an indicator to keyboard users and withholds it from mouse users — up from 0.6% in 2021. That gap is the whole story of the deletion: the outline appeared on a mouse click, a designer objected, and somebody removed it globally instead of scoping it. The neighbouring metric is tabindex, on 63% of mobile sites, of which 4% use positive values — the clearest sign that somebody fought the tab order instead of fixing the markup.

Where focus goes when the route changes is the failure unique to this architecture, and no tool will find it for you. The Almanac is explicit about the limit: "Most automated tests do not test for focus order or keyboard traps. Lighthouse can not tell you that your site is keyboard navigable. All it can do is tell you that it isn't if your site fails some basic tests which are essential." The manual list it recommends includes focus traps, logical tab order, and focus directed to new content on a page — which in a single-page application is precisely the click that changes the URL. A client-side navigation resets nothing: focus stays on the link just activated, which has now disappeared, and the screen reader says nothing about a new page because as far as the browser is concerned none arrived. Somebody has to move focus into the new view and announce it. That is a few lines in a router effect, absent unless written.

Labels are the most measurable gap here and the easiest to close. The Almanac reports that "Unfortunately, 13% of mobile inputs lack accessible names (a significant improvement from 38% in 2022)", that the share of mobile sites using a real label element has fallen to 15% from 19%, and that "57% of mobile sites and 55% of desktop sites use placeholders alone, which can lead to accessibility issues." Placeholder-as-label is the pattern designers like and users lose. Quoting the W3C's placeholder research, the chapter puts the cost precisely: it "can reduce the accessibility and usability of the control for a range of users including older users and users with cognitive, mobility, fine motor skill or vision impairments."

React supplies the piece a correct label needs inside a reusable component: an identifier unique per instance and identical on the server and in the browser. "useId is a React Hook for generating unique IDs that can be passed to accessibility attributes", the React documentation says, and it exists because an incrementing counter does not survive hydration — "With server rendering, useId requires an identical component tree on the server and the client. If the trees you render on the server and the client don't match exactly, the generated IDs won't match" (React, useId, react.dev). It is also firm about the misuse people try first: do not call it to generate keys in a list.

Anything that changes without a navigation has to be announced, and a React application changes constantly. A validation error, a toast, a filter that quietly takes a result count from forty to three — a sighted user sees all of it and a screen reader user receives none unless the region carrying the message is marked as live. Adoption is among the chapter's lowest figures: "We found that 29% of desktop pages use live regions with the aria-live attribute (up from 23% in 2022) and 28% of mobile pages use aria-live (up from 22% in 2022)." Decide the regions before the features — one polite for status, one assertive for errors, both mounted for the life of the page rather than created at the instant they have something to say.

Two settings belong to the user, and React applications override both by accident. Zoom is the first: "WCAG requires that text in a website must be resizable up to at least 200% without any loss in content or functionality", and the Almanac found 16% of mobile sites disabling scaling outright. Sizing text in pixels, which 65% of desktop pages still do against 4% using rem, decides whether a user who raised their browser default sees any difference. Motion is the second: "We discovered that over 50% of mobile sites include the prefers-reduced-motion media query, up from 34% in 2022" — the fastest-improving number in the chapter, and cheap to clear in a codebase that already centralises its transitions.

The afternoon that finds most of the six, cheapest first. Put the mouse away and complete one entire task with the keyboard — sign in, search, open the record, change it, save it — noting every point where you cannot see what is focused or cannot reach a control. Change route and immediately press tab: wherever focus lands is what a keyboard user gets on every navigation for the life of the product. Zoom to 200% and look for content that has left the screen. Switch on reduced motion and forced-contrast mode. Only then run an automated audit, treating what it reports as the floor rather than the result. Finish with twenty minutes of a screen reader on the flow that carries money.

This belongs in an architecture conversation rather than on a QA checklist because four of the six failures are decided once, in shared code, then repeated everywhere. The button primitive, the modal, the form field, the router's navigation effect and the live region are five files: get them right and every screen inherits the behaviour, get them wrong and every screen inherits the defect. That is why a late accessibility programme reads as a rewrite of the component library rather than a pass over the pages, and why the Almanac ends where it does: "Many of these accessibility problems could be avoided if designers and developers integrated accessibility considerations from the start rather than treating them as an afterthought."

The constraint bites hardest where the audience is not optional. A province-wide teacher training programme we built with the Government of Punjab had to work offline, across multiple languages, on low-end Android hardware — constraints that overlap this list almost exactly, because both refuse to assume the user has the device or the senses the developer has. Two adjacent pieces cover what this one leaves out: designing an interface for a system that is sometimes wrong is the design half for AI features, and what a streaming AI response breaks in React is where the live-region question gets hardest, because announcing every token is worse than announcing none.

A React development company should be able to tell you which of the six your application already does, and should say plainly which answers came from a tool and which came from somebody using the keyboard — only one of those survives a procurement review. If the application is one you inherited rather than wrote, what to check before you inherit a React codebase is the wider pass, and accessibility is the item most often missing from it entirely, because nothing about the running application reveals its absence.

Find this useful? Tell Google to show you more of it.

Let's put AI to work in your business.

A 30-minute call. You bring the workflow or the roadmap — we'll tell you what's feasible, what it costs, and what we'd build first.

Book a call