usability.cat
Issue Wiki

Form Validation Issues

Your form says 'invalid input' but won't tell visitors what's actually wrong. That's like a teacher marking your test wrong without circling the answers.

What is this?

Imagine a teacher handing back your test covered in red X marks — but not telling you which answers were wrong or why. That is exactly what a form with bad validation does. Your visitor fills in ten fields, hits submit, and gets a vague "Please fix the errors" banner at the top. Which errors? Where? Good luck figuring that out.

Why it matters

  • For your visitors: They are already doing you a favour by filling out your form. When something goes wrong and your form gives them zero useful feedback, frustration kicks in fast. Most people will not play detective — they will close the tab and find someone who makes it easier.
  • For your business: Every abandoned form is a lost signup, a lost sale, or a lost lead. Studies consistently show that clear inline validation can improve form completion rates by 20-40%. That is real money walking out the door.
  • The standard: WCAG 3.3.1 requires that input errors are identified and described to the user in text. Best practice goes further: show errors inline next to the field that needs fixing, use clear language ("Email must include an @ symbol"), and do it in real time when possible.
Clear inline validation
<div>
  <label for="email">Email address</label>
  <input
    id="email"
    type="email"
    aria-describedby="email-error"
    aria-invalid="true"
  />
  <p id="email-error" role="alert" class="error">
    Please enter a valid email address (e.g. you@example.com)
  </p>
</div>
Vague or missing errors
<div class="banner-error">
  Something went wrong. Please check your inputs.
</div>
<form>
  <input type="text" name="email" />
  <!-- No label, no error, no clue -->
</form>

How to fix it

React / Next.js

Use aria-invalid and aria-describedby to connect each input to its error message. Here is a reusable pattern:

function FormField({
  label,
  name,
  type = "text",
  error,
}: {
  label: string;
  name: string;
  type?: string;
  error?: string;
}) {
  const errorId = `${name}-error`;

  return (
    <div className="flex flex-col gap-1">
      <label htmlFor={name} className="text-sm font-medium">
        {label}
      </label>
      <input
        id={name}
        name={name}
        type={type}
        aria-invalid={!!error}
        aria-describedby={error ? errorId : undefined}
        className={`rounded border px-3 py-2 ${error ? "border-red-500" : "border-neutral-300"}`}
      />
      {error && (
        <p id={errorId} role="alert" className="text-sm text-red-500">
          {error}
        </p>
      )}
    </div>
  );
}

Plain HTML

<form novalidate>
  <div>
    <label for="email">Email address</label>
    <input id="email" type="email" required aria-describedby="email-error" />
    <p id="email-error" role="alert" class="error" hidden>
      Please enter a valid email (e.g. you@example.com)
    </p>
  </div>

  <button type="submit">Sign up</button>
</form>

<script>
  document.querySelector("form").addEventListener("submit", (e) => {
    const email = document.getElementById("email");
    const error = document.getElementById("email-error");
    if (!email.validity.valid) {
      e.preventDefault();
      email.setAttribute("aria-invalid", "true");
      error.hidden = false;
    }
  });
</script>
High impactforms~2 paws

Vague validation is the number one form killer. The cat has zero patience for "invalid input" with no explanation. Fix your error messages, watch your conversions climb.

How the cat scores this

The cat submits your form with intentionally invalid data and checks three things: (1) are error messages shown next to the offending fields, not just in a banner at the top? (2) do the messages actually explain what went wrong? (3) are aria-invalid and aria-describedby used so screen readers can announce errors? Forms with no visible error feedback or only generic messages get flagged hard.

Further reading

On this page