Native validation without JavaScript
HTML5 form validation lets the browser enforce rules declaratively through
attributes, exposing the result via the Constraint Validation API. This reference
covers every validation attribute, the ValidityState flag it sets, and the API
methods — plus a live tester for the pattern attribute.
The complete set of validation attributes
| Attribute | Applies to | ValidityState flag |
|---|---|---|
required | All except hidden, range, color, submit | valueMissing |
pattern | text, search, url, tel, email, password | patternMismatch |
min | number, range, date, month, week, time, datetime-local | rangeUnderflow |
max | Same as min | rangeOverflow |
minlength | text, search, url, tel, email, password, textarea | tooShort |
maxlength | Same as minlength | tooLong |
step | number, range, and temporal types | stepMismatch |
type itself | Numeric and email types | typeMismatch |
The valid flag is only true when all other flags are false and the control is not suffering a custom validity message.
How it works
Validation attributes constrain a control; on submit (or via a script call) the
browser checks each constraint and sets the matching ValidityState flag:
<input type="text" required minlength="3" maxlength="20"
pattern="[a-z0-9_]+" title="lowercase, digits, underscore">
const input = form.elements.username;
if (!input.checkValidity()) {
console.log(input.validity); // { patternMismatch: true, ... }
}
input.setCustomValidity(taken ? "Username is taken" : "");
form.reportValidity(); // shows the bubble on the first invalid field
The pattern regex is implicitly anchored to the whole value (^(?:...)$), so it
must match the entire string, not a substring.
The Constraint Validation API
Three methods let you drive validation from JavaScript:
checkValidity()— returnstrueorfalseand fires aninvalidevent on failure, but shows no UI.reportValidity()— same ascheckValidity()but additionally shows the browser’s native validation bubble on the first failing field and focuses it.setCustomValidity(message)— injects a custom error message intoValidityState.customError. Pass an empty string to clear the custom error and mark the field valid again.
Called on the <form> element, checkValidity() and reportValidity() validate every control in the form.
Practical example — a username field
<form id="signup">
<label>
Username
<input
id="uname"
type="text"
required
minlength="3"
maxlength="20"
pattern="[a-z0-9_]+"
title="3–20 lowercase letters, digits or underscores"
autocomplete="username">
</label>
<p id="uname-error" role="alert" hidden></p>
<button type="submit">Register</button>
</form>
const field = document.getElementById('uname');
const errorEl = document.getElementById('uname-error');
field.addEventListener('blur', () => {
if (!field.validity.valid) {
errorEl.textContent = field.validationMessage;
errorEl.hidden = false;
} else {
errorEl.hidden = true;
}
});
Key gotchas
patternonly fires on non-empty values — always pair it withrequiredif you also want to forbid blanks.- Always add
titlealongsidepattern; the browser shows it in the native error bubble as the reason. min/maxondateinputs use ISO format (YYYY-MM-DD), not localised display strings.step="5"on a number input rejects7because 7 is not a multiple of step from themin(or 0); usestep="any"to allow arbitrary decimals.novalidateon the<form>orformnovalidateon a submit button completely bypasses native checks — useful for save-draft buttons.- Style validated fields with
:valid,:invalid,:required, and the newer:user-invalid(only applies after the user has interacted with the field, avoiding ugly red outlines on untouched forms).