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 |
<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?
Browsers ignore patternClient-side checks can be bypassed with DevTools or direct HTTP requestsServers can't read HTML attributesIt 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.