On this page
Validation is how a form makes sure the data it receives is complete and sensible: the email looks like an email, the required fields aren't empty, the date isn't in the past. Good validation helps people fix mistakes before they hit Submit. Bad validation nags, confuses and costs you submissions.
This guide explains every layer of HTML form validation and how they fit together.
The three layers of validation
- Built-in HTML validation — attributes like
requiredandtype="email". Free, fast and accessible. - JavaScript validation — custom rules and custom messages, using the browser's Constraint Validation API.
- Server-side validation — the only layer you can trust, because anyone can bypass the first two.
You want all three. The first two are for user experience; the third is for correctness and security.
Layer 1: Built-in HTML validation
Add attributes and the browser checks the field when the form is submitted, shows a message, and refuses to send until it's fixed.
<form action="https://api.formclick.io/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<label for="name">Name</label>
<input id="name" name="name" required minlength="2" maxlength="80">
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<label for="guests">Guests</label>
<input id="guests" name="guests" type="number" min="1" max="10" required>
<label for="code">Promo code (optional)</label>
<input id="code" name="promo" pattern="[A-Z0-9]{6}" title="6 capital letters or digits">
<button type="submit">Book</button>
</form>
| Attribute | What it checks |
|---|---|
required |
The field isn't empty (or a checkbox is ticked, or a radio in the group is chosen) |
type="email", type="url" |
The value has the right format |
minlength, maxlength |
Text length |
min, max, step |
Numbers, dates and times within range and on a valid step |
pattern |
The value matches a regular expression (the whole value, not part) |
accept |
Hints which file types the picker shows (not enforced) |
Full details in HTML Form Attributes Explained.
Layer 2: The Constraint Validation API
Every form control has a validity object describing exactly what's wrong:
const email = document.getElementById('email');
email.validity.valueMissing; // true if required and empty
email.validity.typeMismatch; // true if not a valid email
email.validity.tooShort; // shorter than minlength
email.validity.patternMismatch;
email.validity.rangeUnderflow; // below min
email.validity.valid; // true if everything passes
And a few methods to work with it:
checkValidity()— returnstrue/falsewithout showing anything.reportValidity()— checks and shows the browser's message.setCustomValidity(message)— marks the field invalid with your own message (an empty string clears it).
That last one lets you add rules HTML can't express, like "passwords must match":
const password = document.getElementById('password');
const confirm = document.getElementById('confirm');
confirm.addEventListener('input', function () {
confirm.setCustomValidity(
confirm.value === password.value ? '' : 'Passwords do not match.'
);
});
The browser now treats a mismatch like any other validation error — the form won't submit and the message appears. Read more in How to Display Custom Form Validation Messages.
Styling valid and invalid fields
CSS has pseudo-classes for validation state:
:valid/:invalid— match immediately, even before the user types. Using:invalidpaints an empty required form red on page load.:user-valid/:user-invalid— match only after the user has interacted with the field. These are what you usually want.
input:user-invalid {
border-color: #dc2626;
}
input:user-invalid + .error {
display: block;
}
input:user-valid {
border-color: #16a34a;
}
When validation should happen
| Moment | Good for |
|---|---|
| On submit | Always — the final check |
| On blur (leaving a field) | Most fields — people have finished typing |
| While typing | Only for helpful, positive feedback (password strength, characters left) |
Showing "Invalid email" after the first keystroke is the most common validation anti-pattern. Wait until the person leaves the field or submits.
Making validation accessible
- Keep messages next to the field and link them with
aria-describedby. - Set
aria-invalid="true"on fields with errors when you manage errors yourself. - Don't rely on colour — always include text.
- Move focus to the first invalid field on submit so keyboard and screen-reader users know where to go.
- Summarise errors at the top of long forms, with links to each field.
Layer 3: Server-side validation
Browser validation can be skipped by disabling JavaScript, editing the page, or sending a request directly with a script. Your server — or form backend — must validate everything again: required fields, formats, lengths, allowed values and file types.
If you use Formclick, submissions are checked on the server, filtered for spam and limited in size before they reach your inbox or integrations. You still decide what fields mean — for example, a booking system should reject dates in the past even if the form already did.
More on this split in Client-Side vs Server-Side Form Validation.
Quick checklist
- Use the right
typefor every field. - Add
requiredonly where you truly need the data. - Write helpful
titletext for everypattern. - Style with
:user-invalid, not:invalid. - Validate on blur and submit, not on every keystroke.
- Always validate again on the server.
Next: How to Validate Email Addresses in HTML Forms and How to Validate Phone Numbers in HTML Forms.



