Form Accessibility React
title: "Form Accessibility in React" weight: 16 description: "Learn how to build accessible forms in React: labels linked to inputs, error messages with aria-describedby, live region announcements, validation with aria-invalid, and form state management." date: 2026-06-28 lastmod: 2026-06-28 tags: [accessibility, react]
Accessible forms in React require proper label associations via htmlFor or aria-label, inline error messages linked with aria-describedby, validation indicators with aria-invalid, and live region announcements for form submission feedback.
## What You'll Learn
You will build accessible forms in React, handle validation with accessible errors, manage form state accessibly, and ensure screen readers announce form updates.
## Why It Matters
Forms are critical user interactions. Inaccessible forms prevent users from completing registrations, purchases, and other essential tasks. React's state management makes it easy to create dynamic, accessible forms.
## Real-World Use
A React registration form shows errors inline but does not link them to inputs. Screen reader users cannot associate errors with fields. Adding aria-describedby and aria-invalid makes the form fully accessible.
## Accessible Form Structure
```mermaid
flowchart TD
A[Form Component] --> B[Field Component]
B --> C[label with htmlFor]
B --> D[input with id]
B --> E[error message]
B --> F[aria-describedby]
B --> G[aria-invalid]
E --> F
G --> E
Building Accessible Forms
Create field components that encapsulate label, input, and error handling.
function FormField({ label, name, type = 'text', error, ...props }) {
const fieldId = `field-${name}`;
const errorId = `error-${name}`;
return (
<div className="form-field">
<label htmlFor={fieldId}>{label}</label>
<input
id={fieldId}
name={name}
type={type}
aria-invalid={!!error}
aria-describedby={error ? errorId : undefined}
{...props}
/>
{error && (
<p id={errorId} role="alert" className="form-error">
{error}
</p>
)}
</div>
);
}
function RegistrationForm() {
const [formData, setFormData] = useState({ email: '', password: '' });
const [errors, setErrors] = useState({});
const validate = (name, value) => {
switch (name) {
case 'email':
return value.includes('@') ? undefined : 'Please enter a valid email';
case 'password':
return value.length >= 8 ? undefined : 'Password must be at least 8 characters';
default:
return undefined;
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
setErrors(prev => ({ ...prev, [name]: validate(name, value) }));
};
const handleSubmit = (e) => {
e.preventDefault();
const newErrors = {};
Object.keys(formData).forEach(key => {
const error = validate(key, formData[key]);
if (error) newErrors[key] = error;
});
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
announceSuccess('Registration submitted successfully');
}
};
return (
<form onSubmit={handleSubmit} noValidate>
<FormField
label="Email address"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
error={errors.email}
autoComplete="email"
/>
<FormField
label="Password"
name="password"
type="password"
value={formData.password}
onChange={handleChange}
error={errors.password}
autoComplete="new-password"
/>
<button type="submit">Register</button>
</form>
);
}
// Live region for form submission
function announceSuccess(message) {
const announcer = document.getElementById('form-announcer');
if (announcer) {
announcer.textContent = '';
setTimeout(() => {
announcer.textContent = message;
}, 50);
}
}
Common Mistakes
- Using placeholder instead of label
- Not linking errors to inputs with aria-describedby
- Not using aria-invalid on invalid fields
- Showing errors only in a summary at the top of the form
- Clearing form state without announcing the change
- Not using noValidate to prevent browser validation interference
- Forgetting autocomplete attributes for browser autofill
Practice and Challenge
Practice 1: Create a reusable FormField component with accessibility. Practice 2: Add aria-describedby linking errors to inputs. Practice 3: Implement live region for form submission feedback. Practice 4: Add aria-invalid to invalid fields. Practice 5: Test the form with a screen reader.
Challenge: Build a multi-step registration form in React with accessibility at every step. Include: field-level validation with inline errors, step progress indicator with aria-current, focus management on step transitions, live region announcements for step changes, and a summary review step before final submission.
FAQ
Mini Project
Create a React form library with accessible components: FormField (text, email, password, textarea), SelectField, CheckboxGroup, RadioGroup, and SubmitButton. Each component should include proper labeling, error handling, aria attributes, and keyboard support. The library should include a demo form that passes axe-core testing.
What's Next
React Router Accessibility covers focus management in React Router navigation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro