Building Accessible Forms in React: A 2026 Checklist
Twelve accessibility checks I apply to every React form before shipping — labels, error states, keyboard nav, screen reader support, and the ones most teams miss.
Why this matters
If your form is inaccessible, a meaningful chunk of users (estimated 15-20 percent) struggle or can't complete it. That's not a CSR issue — it's a conversion issue.
Accessible forms are also more usable for everyone. Keyboard navigation is faster. Clear error messages save support tickets. Proper labels mean better autofill, faster checkouts.
This is the 12-point checklist I apply to every form before merging.
1. Every input has a real label element
Not a placeholder. Not aria-label as a primary label. A real label element.
Placeholders disappear when users type. Labels persist. Screen readers can't reliably announce placeholders as field names. Use either label wrapping the input, or label with htmlFor pointing to input id.
2. Required fields are marked clearly
Use both the required attribute and a visible indicator (an asterisk, "Required" text). The asterisk is visual; "required" is announced via sr-only span.
3. Error messages are associated with inputs
Use aria-invalid on the input and aria-describedby pointing to the error message's id. Use role="alert" on the error so it's announced when it appears.
4. Error messages explain what's wrong, not just that it's wrong
Vague errors waste users' time. Specific errors let them fix and move on.
Bad: "Invalid input"
Good: "Email must include an at sign"
Good: "Password must be at least 8 characters"
Good: "Card number should be 16 digits"
5. Inline validation fires at the right moment
The pattern I use:
Validating on every keystroke (before the user finishes) is annoying. Validating only on submit means users have to refer back to multiple fields. Blur plus change-after-error is the goldilocks pattern.
6. Tab order is logical
Test by tabbing through your form with the keyboard only. Does it follow the visual order? Does it land on submit?
Custom tab orders via tabIndex 1, tabIndex 2 are almost always wrong. Use HTML's natural order; only use tabIndex minus 1 to remove items from the tab order (e.g., decorative icons).
7. Submit button is reachable by keyboard
Some custom-styled buttons end up as div with onClick. Don't. Buttons are inherently focusable. Divs aren't (unless you add tabIndex and key handlers, which you shouldn't have to).
8. Submitting the form via Enter works
Test: focus an input, press Enter. Does the form submit?
If not, you're missing form with onSubmit. Wrap your inputs in a form element. Enter-to-submit is muscle memory for many users.
9. Focus moves to errors on submit
If the form fails validation, the first error field should receive focus. This is how keyboard and screen-reader users know "you have errors here." Programmatically focus the first invalid input.
10. Color is not the only error indicator
Color-blind users may miss "this field's border is red." Pair color with an icon, an error message below, or the aria-invalid attribute.
The "is this field in error?" signal should be available via multiple channels.
11. Inputs have appropriate types
Use type="email" for email (mobile keyboard shows the at key), type="tel" for phone (numeric keyboard), type="number" for numbers, type="url" for URLs (mobile keyboard shows slash and dot), type="search" for search (clear button), type="date" for date (native picker), type="password" for passwords.
Wrong type means worse mobile UX. Type email is a 10-second change that improves form completion by ~5-10 percent on mobile.
12. Autocomplete is enabled
Use autoComplete="email" on email, autoComplete="given-name" on first name, autoComplete="cc-number" on credit card, autoComplete="address-line1" on address.
Browser autofill is the single biggest accessibility or usability win that gets disabled by accident. Use the standard autoComplete values from WHATWG.
What I always skip
Things I see overdone:
Quick audit
For every form before merging:
If you pass these five tests, you've shipped a form most users will breeze through.
TL;DR
If your forms need an accessibility pass before launching, contact me.
You might also like
Real-Time Apps with Next.js Server Actions and WebSockets in 2026
When Server Actions are enough, when you need a WebSocket layer, and how to wire Pusher / Soketi / Ably into a Next.js 14 App Router project without breaking SSR.
TypeScript Generics for React Engineers: A Practical Guide
The 6 generic patterns I use weekly on React + Next.js codebases — typed hooks, polymorphic components, discriminated unions, infer, constraints — without the academic noise.
React Server Components in Next.js 14: Production Patterns That Work
When to use Server Components vs Client Components in Next.js 14, the patterns that survive production, and the foot-guns I keep tripping over after shipping 8+ App Router projects.