Skip to content

React Tutorial Forms and Validation

Accessible Validation Messages in React Forms

Lesson 77 of 156 2 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

A validation message is useful only if the user notices it, understands it and can reach it, including users of screen readers and keyboards. This lesson covers the markup and the wording.

Connect the message to the field

Two ARIA attributes tie an error to its input:

  • aria-invalid="true" tells assistive technology the value is wrong.
  • aria-describedby="id" points at the element with the message, which is read after the label.
App.jsx
JSX
function App() {
  const [email, setEmail] = React.useState("");
  const [checked, setChecked] = React.useState(false);
  const valid = /^\S+@\S+\.\S+$/.test(email);
  const showError = checked && !valid;

  return (
    <form noValidate onSubmit={(e) => { e.preventDefault(); setChecked(true); }}>
      <label htmlFor="email">Email</label>
      <input
        id="email"
        aria-label="Email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        aria-invalid={showError ? "true" : undefined}
        aria-describedby={showError ? "email-error" : undefined}
      />
      {showError && (
        <p id="email-error" style={{ color: "#b71c1c", margin: "4px 0" }}>
          Enter an email address like name@example.com
        </p>
      )}
      <button type="submit">Check</button>
    </form>
  );
}

A screen reader will say something like “Email, edit text, invalid entry, Enter an email address like name@example.com”.

Announce errors that appear later

Content that appears dynamically is not announced unless it is in a live region. Use:

  • role="alert" for an important message that should interrupt (“Your payment failed”).
  • role="status" for polite updates (“Saved”).
  • A visible error summary at the top of the form after a failed submit.

Move focus to the first problem

After a failed submit, send keyboard and screen-reader users to the first invalid field, so they do not have to hunt:

App.jsx
JSX
function App() {
  const [name, setName] = React.useState("");
  const [error, setError] = React.useState("");
  const nameRef = React.useRef(null);

  function handleSubmit(e) {
    e.preventDefault();
    if (!name.trim()) {
      setError("Enter your name");
      nameRef.current.focus();      // move focus to the field with the error
      return;
    }
    setError("");
  }

  return (
    <form noValidate onSubmit={handleSubmit}>
      <label>
        Name
        <input
          ref={nameRef}
          aria-label="Name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          aria-invalid={error ? "true" : undefined}
          aria-describedby={error ? "name-error" : undefined}
        />
      </label>
      {error && <p id="name-error" role="alert">{error}</p>}
      <button type="submit">Save</button>
    </form>
  );
}

For forms with several errors, focus the first invalid field and list all errors in a summary at the top, each linking to its field.

Do not rely on colour alone

Red text is not enough for people who cannot see the colour. Add an icon or the word “Error”, and keep sufficient contrast (at least 4.5:1 for normal text against the background).

Write helpful messages

Not helpfulHelpful
“Invalid input”“Enter a date like 25/12/2026”
“Error 422”“That email address is already registered. Try signing in.”
“Password too weak”“Use at least 12 characters. A short phrase works well.”
“Required”“Enter your email address”
“You failed to enter a correct value”“The postcode looks incorrect. Check it and try again.”

Good messages say what is wrong, how to fix it, and do not blame the user. Show the rule (such as password length) before the user makes an error, in the hint text under the label.

Keep what the user typed

Never clear a field because it has an error. Let people correct the value instead of retyping everything.

Key points

  • Use aria-invalid and aria-describedby to connect messages to inputs, and live regions for dynamic messages.
  • Move focus to the first error after a failed submit.
  • Write messages that explain how to fix the problem, and never rely on colour alone.