Accessibility in React: the twelve things that catch 80% of issues
Most accessibility failures in React apps come from a handful of repeated patterns. Fix these twelve and the audit gets short.
A production React form needs uncontrolled inputs or a form library for performance, a shared schema validating on both client and server, error messages linked to inputs with aria-describedby, a disabled-and-labelled submit state, and a focus move to the first error on failed submission. Native form semantics do most of the work if you let them.
Uncontrolled by default; controlled when a value needs to drive other UI on every keystroke.
A controlled input re-renders the form on every character. For a login form that is irrelevant. For a 40-field application form with conditional sections, it is the difference between a snappy and a laggy experience on a mid-range phone.
Form libraries like React Hook Form default to uncontrolled inputs with refs, subscribing only the components that need a value. That is why they scale to large forms while a naive useState-per-field approach does not.
Define the schema once and use it in both places. The client uses it for immediate feedback; the server uses it as the actual boundary. If they are separate implementations, they will drift, and the drift will be a security bug rather than a cosmetic one.
// lib/schemas/contact.ts — imported by the form and the server action
export const contactSchema = z.object({
name: z.string().min(2, "Please enter your name"),
email: z.string().email("Enter a valid email address"),
budget: z.enum(["<5k", "5k-15k", "15k+"]),
message: z.string().min(20, "Tell us a little more — at least 20 characters"),
});
export type ContactInput = z.infer<typeof contactSchema>;<label htmlFor="email">Work email</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
aria-invalid={Boolean(errors.email)}
aria-describedby={errors.email ? "email-error" : undefined}
/>
{errors.email && <p id="email-error" role="alert">{errors.email.message}</p>}| Option | Good for | Notes |
|---|---|---|
| Native form + server action | Simple forms, progressive enhancement | Works without JavaScript; least code |
| React Hook Form + Zod | Medium to large forms, complex validation | Uncontrolled by default, excellent performance |
| TanStack Form | Type-heavy forms with cross-field logic | Strong inference, newer ecosystem |
| Hand-rolled useState | Two or three fields | Fine at small scale, painful past that |
Not initially. Validate on blur, then switch that field to on-change once it has an error so the user sees it clear as they fix it. Immediate errors while typing feel like being interrupted.
Yes, for responsiveness. The server check is the security boundary; the client check saves a round trip and gives instant feedback. Share the schema so both are the same rules.
Announce step changes in a live region, move focus to the new step heading, keep progress visible, and never lose entered data when navigating backwards.
No. They disappear when the field has content, typically fail contrast requirements, and are not reliably announced by assistive technology. Use a visible label.
Harshal Patel
Founder & Lead Engineer, ROVQIX
Harshal leads engineering at ROVQIX, where he has shipped production Next.js, Node.js and PostgreSQL systems for startups, SaaS teams and ecommerce brands. He writes about the trade-offs behind architecture decisions rather than the framework of the week.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Most accessibility failures in React apps come from a handful of repeated patterns. Fix these twelve and the audit gets short.
A server action is a public HTTP endpoint with nicer syntax. Treat it like one and they are excellent; forget that and you have shipped an unauthenticated API.
Not a compliance document. This is the list we actually work through before a client site handles its first real user.
No spam. Just the occasional case study and craft breakdown.