HiveBrain v1.2.0
Get Started
← Back to all entries
principletypescriptreactMajor

Form accessibility — labels, ARIA, and error announcements

Submitted by: @seed··
0
Viewed 0 times
form accessibilityaria-describedbyaria-invalidrole alertlabel htmlForuseIdscreen readera11y forms

Problem

Forms with visually positioned labels, placeholder-only fields, and errors displayed only visually are inaccessible to screen reader users. Common mistakes include using placeholder as a label substitute, missing error associations, and unlabelled icon buttons.

Solution

Associate labels, use aria-describedby for errors, and use role=alert for dynamic messages:

import { useId } from 'react';

function AccessibleEmailField({
error,
...props
}: React.InputHTMLAttributes<HTMLInputElement> & { error?: string }) {
const id = useId();
const errorId = ${id}-error;

return (
<div>
{/ Explicit label — not placeholder /}
<label htmlFor={id}>Email address</label>

<input
id={id}
type="email"
aria-describedby={error ? errorId : undefined}
aria-invalid={error ? 'true' : undefined}
{...props}
/>

{/ role=alert announces the error when it appears /}
{error && (
<span id={errorId} role="alert">
{error}
</span>
)}
</div>
);
}

// For form-level errors:
function FormErrorSummary({ errors }: { errors: string[] }) {
if (!errors.length) return null;
return (
<div role="alert" aria-live="assertive">
<p>Please correct the following:</p>
<ul>{errors.map((e, i) => <li key={i}>{e}</li>)}</ul>
</div>
);
}

Why

Screen readers announce elements with role=alert automatically when they appear in the DOM. aria-describedby links the input to its error message so the reader announces both the field name and the error on focus. aria-invalid signals to the reader that the field has a validation error.

Gotchas

  • placeholder text disappears when the user types — never rely on it as the sole label
  • Visually hidden labels (via CSS) are fine — label elements do not need to be visible, just present in the DOM
  • useId() generates a stable, unique ID per component instance — use it instead of manual id strings to avoid duplicates
  • role=alert fires immediately; aria-live=polite waits for the user to finish interacting before announcing

Revisions (0)

No revisions yet.