HTML Tutorial · HTML5 Features · Lesson 26 of 34

HTML Form Validation API

Use built-in validation attributes, custom messages with setCustomValidity, and the :user-invalid style, then still validate on the server.

Built-in validation

Browsers check form fields before submitting when you use attributes such as required, type="email", min, max, minlength, maxlength and pattern. Invalid fields block submission and show a message. (See input types for the basics.)

The Constraint Validation API

JavaScript can read and control validation:

API Purpose
input.validity which rule failed (valueMissing, typeMismatch, patternMismatch, tooShort...)
input.setCustomValidity("message") mark a field invalid with your own message ("" clears it)
form.checkValidity() / reportValidity() check all fields (and show messages)
novalidate on <form> turn off the browser's own UI to show custom errors
Custom messages with the Constraint Validation APITry it yourself
<style>
  input:user-invalid { border: 2px solid crimson; }
  .error { color: crimson; font-size: .9em; }
</style>
<form id="signup" novalidate>
  <p><label>Username <input name="user" required minlength="3" pattern="[a-z0-9_]+"></label></p>
  <p><label>Password <input name="pw" type="password" required minlength="8"></label></p>
  <p><label>Repeat password <input name="pw2" type="password" required></label></p>
  <p class="error" id="errors" aria-live="polite"></p>
  <button>Create account</button>
</form>
<script>
  const form = document.getElementById("signup");
  // Real pages listen for the form's "submit" event; this preview blocks form submission, so we use the button's click.
  form.querySelector("button").addEventListener("click", (e) => {
    e.preventDefault();
    const { pw, pw2, user } = form.elements;
    pw2.setCustomValidity(pw.value === pw2.value ? "" : "Passwords don't match");
    if (user.validity.patternMismatch) user.setCustomValidity("Use lowercase letters, digits and _ only");
    else user.setCustomValidity("");
    const messages = [...form.elements].filter((el) => el.willValidate && !el.checkValidity())
      .map((el) => `${el.name}: ${el.validationMessage}`);
    document.getElementById("errors").textContent = messages.length ? messages.join(" | ") : "All good: send it to the server, which validates again.";
  });
</script>

Styling: :user-invalid

:invalid matches immediately, so empty required fields look like errors before the user has typed. :user-invalid matches only after the user has interacted with the field, which is friendlier.

Always validate on the server too

Client-side validation is a convenience. Anyone can bypass it (with DevTools or a direct HTTP request), so the server must check every rule again.

Test yourself

Why must the server validate form data even when the HTML has required and pattern attributes?

  1. Browsers ignore pattern
  2. Client-side checks can be bypassed with DevTools or direct HTTP requests
  3. Servers can't read HTML attributes
  4. It makes forms load faster
Show answer

Answer: Client-side checks can be bypassed with DevTools or direct HTTP requests

Client validation improves the user experience; only server validation protects your data.