Quick Answer

required, type, pattern, min, max and maxlength give you validation with accessible messages and no JavaScript. Use them first, add JavaScript only for rules they cannot express, and always validate again on the server.

What you get for free

<form>
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>

  <label for="age">Age</label>
  <input id="age" name="age" type="number" min="13" max="120" required>

  <label for="roll">Roll number</label>
  <input id="roll" name="roll" pattern="[0-9]{6}"
         title="Six digits" required>

  <button>Submit</button>
</form>

That validates on submit, blocks it while invalid, focuses the first problem field and shows a message — with no JavaScript, and announced correctly by screen readers.

The attributes worth knowing: required, type (email, url, number, tel, date), min and max, minlength and maxlength, step, and pattern for a regular expression.

type does double duty on mobile: type="email" and type="tel" bring up the appropriate keyboard, which is a real usability gain that JavaScript validation cannot provide.

Every input needs a <label> with a matching for. Placeholder text is not a label — it disappears on typing and is poorly announced.

Styling validity without shouting too early

input:invalid { border-color: #dc2626; }
input:valid   { border-color: #16a34a; }

The problem: an empty required field is invalid immediately, so the form is covered in red before the user types anything.

:user-invalid fixes it by applying only after the user has interacted with the field:

input:user-invalid { border-color: #dc2626; }
input:user-invalid + .error { display: block; }

That matches what people expect — tell me after I have tried, not before I have started.

Do not rely on colour alone. A red border is invisible to some users, so pair it with a text message and an icon.

Custom messages and rules

Default browser messages are generic and not translatable. Override them with the Constraint Validation API:

const roll = document.getElementById("roll");

roll.addEventListener("input", () => {
  roll.setCustomValidity(
    roll.validity.patternMismatch
      ? "Roll number must be exactly six digits"
      : ""
  );
});

Setting a non-empty string marks the field invalid with that message; setting "" clears it. You must clear it, or the field stays permanently invalid — a common bug.

The same API handles rules HTML cannot express, such as comparing two fields:

confirm.setCustomValidity(
  confirm.value === password.value ? "" : "Passwords do not match"
);

Useful properties on field.validity: valueMissing, typeMismatch, patternMismatch, rangeOverflow and tooShort, so you can tailor the message to the actual failure.

Client validation is not security

The point that matters most.

Everything above is a convenience for honest users. It tells someone they mistyped their email before a round trip. It stops nobody who does not want to be stopped.

Anyone can open developer tools and delete the required attribute, or skip your page entirely and POST directly to your endpoint with any data at all. HTML validation exists in the browser, and the browser is under the user's control.

Every rule must be enforced again on the server. Not "also" — the server check is the real one, and the client check is the nicety. See designing a REST API for returning validation errors properly.

This is not hypothetical. Assuming client validation held is how invalid data reaches production databases, and how negative quantities end up in orders.

Form usability details that matter

  • Use autocomplete attributesautocomplete="email", "given-name", "one-time-code". Browsers and password managers fill these correctly, which measurably increases completion.
  • Do not disable paste on password fields. It breaks password managers and pushes people towards weaker passwords.
  • Validate email loosely. Strict regular expressions reject valid addresses. type="email" is a sensible check; only sending a message proves an address works.
  • Keep error messages next to the field and describe how to fix it — "Password needs at least 8 characters" beats "Invalid input".
  • Never clear the form on a failed submit. Re-typing everything is the fastest way to lose a user.

Frequently Asked Questions

Is HTML validation enough on its own? For user experience it covers most cases. For security it is worthless, since anyone can bypass it. Every rule must be enforced again on the server.
Why does my form show errors before the user types? The :invalid selector matches empty required fields immediately. Use :user-invalid instead, which applies only after the user has interacted with the field.
How do I write a custom validation message? Call setCustomValidity with your message, and call it with an empty string when the value becomes valid. Forgetting to clear it leaves the field permanently invalid.
Should I write a regex for email addresses? Avoid strict patterns, which reject valid addresses. type="email" gives a reasonable check, and only sending a confirmation message proves an address is real.
Why does type matter on mobile? It selects the on-screen keyboard. type="email" and type="tel" show appropriate layouts, which is a usability improvement JavaScript validation cannot provide.