Sent empty, or with “a” typed into the email field—adding a few HTML attributes hands most of this to the browser. Start there.
What attributes alone can do
<form>
<label for="name">Name</label>
<input type="text" id="name" name="name" required>
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<label for="zip">Postcode</label>
<input type="text" id="zip" name="zip" pattern="\d{5}" placeholder="12345">
<button type="submit">Send</button>
</form>That alone makes the browser stop an empty submission and say so.
| Attribute | What it checks |
|---|---|
required | That it isn’t empty |
type="email" | That it looks like an email address |
type="url" | That it looks like a URL |
type="number" + min / max | That it’s a number, within a range |
minlength / maxlength | Character count limits |
pattern="…" | That it matches a shape you define (a regular expression) |
Showing “valid” and “invalid” in CSS
The browser exposes the state so you can style it.
input:user-invalid {
border-color: #e5484d; /* red once they've typed something invalid */
}
input:user-valid {
border-color: #3aa76d; /* green once it's valid */
}AccessibilityReadable colors, unreadable colors (contrast)More on not relying on colour, in the accessibility courseAdding your own checks in JavaScript
Checks the attributes can’t express—“do these two password fields match?”—go in JavaScript.
const form = document.querySelector("#signup");
const pass = document.querySelector("#pass");
const confirm = document.querySelector("#confirm");
form.addEventListener("submit", function (event) {
if (pass.value !== confirm.value) {
event.preventDefault();
document.querySelector("#error").textContent = "The passwords don't match";
}
});To put your own wording in the browser’s own bubble, use setCustomValidity().
confirm.addEventListener("input", function () {
if (confirm.value !== pass.value) {
confirm.setCustomValidity("The passwords don't match");
} else {
confirm.setCustomValidity(""); // empty string means "no problem"
}
});Validation is not a security measure
Writing error messages people can act on
The same error reads very differently depending on the words.
| ✕ Dismissive | ○ Tells them what to do |
|---|---|
Invalid input | Check the format of the email address |
Bad value | The postcode should be five digits, like 12345 |
Required | Please enter your name |
- Where the problem is (put it next to the field)
- What was expected (shape, length)
- How to fix it
That’s enough. Equally important: don’t wipe what they typed. A form that empties itself on every error is a form nobody finishes.
AccessibilityAlways give a form a labelTying labels to fields properlyTipsMaking your contact form actually sendMaking the form actually send once it passesSummary
- Start with HTML attributes (
required,type,pattern)—the browser checks for free - Show state with
:user-invalidand:user-valid, and never with colour alone - Browser-side checks are courtesy, not security. The receiving side has to validate
How carefully you handle validation shows up directly in how many people finish your form.