Back to React
React
easy
mid

How do you design good validation UX in complex forms?

Validate at the right moments (on blur / on submit, then on change once a field is touched/errored), show clear inline messages near the field, never block typing, surface a submit-time summary, mirror server validation, and make errors accessible (aria-invalid, aria-describedby, focus management).

6 min read·~20 min to think through

Good validation UX is about when you validate and how you communicate — not just the rules. The goal: catch errors helpfully without nagging.

Validation timing — the core decision

  • Don't validate on every keystroke from the start — flagging "invalid email" while someone is still typing the first character is hostile.
  • Validate on blur (or on submit) for the first signal — the user finished with the field.
  • After a field has errored, switch to on-change — so they see it clear as they fix it. ("Validate on blur, re-validate on change once touched.")
  • Always validate on submit — and block submission if invalid.
  • Async validation (uniqueness checks) — debounced, with a pending indicator.

Communicating errors

  • Inline, next to the field — not just a list at the top.
  • Specific and actionable — "Password needs at least 8 characters," not "Invalid input."
  • Visually clear — red border + icon + message; don't rely on color alone.
  • Show success sparingly for high-stakes fields (a green check on a confirmed unique username).
  • Submit-time summary — on a failed submit, a summary at the top and inline errors; focus/scroll to the first error.

Don't punish the user

  • Never block typing — let them type anything; validate after.
  • Preserve input on a failed submit — never clear the form.
  • Disable vs allow-and-explain — prefer letting them click submit and showing what's wrong over a permanently-disabled button with no explanation (a disabled button gives no feedback about why).

Accessibility (frequently the differentiator)

  • aria-invalid="true" on errored fields.
  • aria-describedby linking the field to its error message so screen readers announce it.
  • Error text in a live region, or move focus to the first error on submit.
  • Errors associated with <label>s and inputs properly.

Server-side validation must mirror client-side

  • Client validation is UX; server validation is correctness/security — never trust the client.
  • Map server validation errors back onto the specific fields, in the same inline style.
  • Handle the case where the server rejects something the client thought was valid.

Tooling

React Hook Form + Zod/Yup: schema-driven rules, mode: 'onTouched' for the blur-then-change behavior, and one schema you can share with the server.

Summary

Validate on blur, re-validate on change once touched, always on submit. Inline, specific, accessible messages. Never block typing, never lose input, always mirror on the server.

Follow-up questions

  • When should validation fire — keystroke, blur, or submit?
  • Should you disable the submit button on an invalid form?
  • How do you make validation errors accessible to screen readers?
  • Why must server-side validation exist even with thorough client validation?

Common mistakes

  • Validating on every keystroke from the first character — nagging the user.
  • Vague messages like 'invalid input' with no guidance.
  • Clearing the form or losing input on a failed submit.
  • No aria-invalid/aria-describedby — errors invisible to screen readers.
  • Trusting client validation and skipping server validation.

Performance considerations

  • Debounce async/expensive validation. With controlled forms, per-keystroke validation can re-render the whole form — React Hook Form's uncontrolled model isolates this. Don't run the full schema on every keystroke for large forms.

Edge cases

  • Async validation still pending when the user hits submit.
  • Server rejects something the client validated as fine.
  • Cross-field validation (passwords match, date ranges).
  • Conditional fields — validate only the visible ones.

Real-world examples

  • A signup form: email validated on blur, password requirements shown live once touched, username uniqueness checked async with a spinner.
  • A checkout form mapping server-side payment validation errors back to the relevant inline fields.

Senior engineer discussion

Seniors nail the timing model (blur first, change after touched, always on submit) and treat accessibility — aria-invalid, aria-describedby, focus management — as core, not optional. They distinguish client validation (UX) from server validation (correctness/security), insist on never blocking typing or losing input, and reach for schema-driven validation shared between client and server.

Related questions