patternjavascriptMajor
Error announcements — making form validation errors accessible
Viewed 0 times
WCAG 2.1 Level A
form errorsaria-invalidaria-describedbyerror announcementvalidation accessibilityWCAG 3.3.1
Problem
Form validation errors appear visually but are not announced to screen readers. The error message is rendered in the DOM but focus remains on the submit button, leaving AT users unaware that submission failed.
Solution
Connect error messages to their inputs using aria-describedby and move focus to the first error or an error summary.
// Input with linked error message
<label for="email">Email</label>
<input
id="email"
type="email"
aria-invalid={hasError}
aria-describedby={hasError ? 'email-error' : undefined}
/>
{hasError && (
<span id="email-error" role="alert">
Enter a valid email address.
</span>
)}
// On submit failure — move focus to error summary
const summaryRef = useRef(null);
function handleSubmit(e) {
const errors = validate(formData);
if (errors.length) {
setErrors(errors);
summaryRef.current?.focus();
}
}
<div ref={summaryRef} tabIndex={-1} role="alert">
<h2>Please fix {errors.length} error(s):</h2>
<ul>{errors.map(e => <li key={e.field}>{e.message}</li>)}</ul>
</div>
// Input with linked error message
<label for="email">Email</label>
<input
id="email"
type="email"
aria-invalid={hasError}
aria-describedby={hasError ? 'email-error' : undefined}
/>
{hasError && (
<span id="email-error" role="alert">
Enter a valid email address.
</span>
)}
// On submit failure — move focus to error summary
const summaryRef = useRef(null);
function handleSubmit(e) {
const errors = validate(formData);
if (errors.length) {
setErrors(errors);
summaryRef.current?.focus();
}
}
<div ref={summaryRef} tabIndex={-1} role="alert">
<h2>Please fix {errors.length} error(s):</h2>
<ul>{errors.map(e => <li key={e.field}>{e.message}</li>)}</ul>
</div>
Why
WCAG 3.3.1 requires errors to be identified and described in text. Without programmatic association, a screen reader user hears only 'email, invalid' with no description of what went wrong.
Gotchas
- aria-describedby supplements the label — both label and error are announced together when the input is focused
- aria-invalid='true' signals the error state but does not announce the error message on its own
- role='alert' on an error message triggers immediate announcement — appropriate for errors, not for every validation hint
- Client-side validation must not replace server-side validation — error handling must account for both
Context
Any form with client-side or server-side validation
Revisions (0)
No revisions yet.