HTML Form Validation
Browsers can validate form input before it’s ever sent to a server, using nothing but HTML attributes. This won’t replace server-side validation (never trust data from the browser alone), but it gives users immediate feedback and stops obviously wrong submissions early.
required
Marks a field as mandatory. The browser blocks submission and shows a built-in message if it’s empty.
<label for="username">Username</label>
<input type="text" id="username" name="username" required />
min and max
For number and date-like inputs, min and max set the allowed range.
<label for="years">Years of experience</label>
<input type="number" id="years" name="years" min="0" max="50" />
<label for="event-date">Event date</label>
<input type="date" id="event-date" name="event-date" min="2026-01-01" max="2026-12-31" />
minlength and maxlength
For text inputs, minlength and maxlength restrict how many characters are allowed.
<label for="password">Password</label>
<input type="password" id="password" name="password" minlength="8" maxlength="64" required />
pattern
pattern takes a regular expression the value must match. Useful for formats that type alone doesn’t cover.
<label for="zip">ZIP code</label>
<input type="text" id="zip" name="zip" pattern="[0-9]{5}" title="Five digit ZIP code" />
Always pair pattern with a title attribute. Browsers show the title text as part of the validation message when the pattern doesn’t match, without it users just see a generic error with no idea what format is expected.
Type-based validation
Some type values validate their own format automatically, no extra attributes needed:
<input type="email" required />
<input type="url" required />
type="email" rejects a value with no @, type="url" requires something that looks like a valid URL.
Styling valid and invalid fields
CSS can target the validation state directly, so you can show feedback without any JavaScript:
input:invalid {
border-color: #d33;
}
input:valid {
border-color: #2a2;
}
/* Only show red after the user has interacted with the field */
input:invalid:not(:placeholder-shown) {
border-color: #d33;
}
That last rule matters: styling every field red with :invalid before the user has typed anything makes a form look broken on page load. Combining it with :not(:placeholder-shown) waits until there’s a value to actually judge.
Custom validation messages
For messages more specific than the browser default, use the setCustomValidity method in JavaScript:
<input type="text" id="username" name="username" required />
const input = document.getElementById("username");
input.addEventListener("input", () => {
if (input.value.includes(" ")) {
input.setCustomValidity("Username cannot contain spaces");
} else {
input.setCustomValidity("");
}
});
Calling setCustomValidity("") clears the error, this is required, or the field stays permanently invalid even after the user fixes it.
Client-side validation is not enough
Built-in HTML validation can always be bypassed, by disabling JavaScript, editing the HTML in dev tools, or sending a request directly to your server without a browser at all. Treat it as a UX improvement, not a security measure. Always validate and sanitise data again on the server.
Common mistakes
- Relying on HTML validation alone and skipping server-side checks.
- Using
patternwithout atitle, leaving users with no idea what format is expected. - Styling
:invalidfields red before the user has had a chance to type anything.
FAQ
Can I turn off browser validation for a form?
Yes, add novalidate to the <form> tag. Useful if you want full control over validation and messaging in JavaScript instead.
<form novalidate>...</form>
Does required work on checkboxes?
Yes. On a single checkbox, required means it must be checked, useful for “I agree to the terms” style confirmations.
What to read next
- Forms : labels, fieldsets, and general form structure
- React Forms : handling validation and submission in a React app