Form Validation Attributes
Native constraint validation — required, type, pattern, min/max, minlength/maxlength, and wiring :invalid with accessible errors.
- html
- form-validation
- forms
Browsers ship a constraint validation API. Attributes like required, type="email", min, max, minlength, maxlength, pattern, and step define rules; submit attempts can block and show UA messages — or you customize with JS while keeping the same engine.
Docs: MDN constraint validation, MDN forms.
Core attributes
<form novalidate id="signup">
<!-- novalidate if you fully custom-handle; otherwise leave UA validation on -->
</form>
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
required
autocomplete="email"
aria-describedby="email-hint email-error"
/>
<span id="email-hint">We’ll send a confirmation link.</span>
<span id="email-error" role="alert" hidden></span>
<label for="age">Age</label>
<input id="age" name="age" type="number" min="13" max="120" step="1" required />
<label for="username">Username</label>
<input
id="username"
name="username"
required
minlength="3"
maxlength="30"
pattern="[a-zA-Z0-9_]+"
title="Letters, numbers, and underscore only"
/>
| Attribute | Role |
|---|---|
required |
Non-empty (details vary by type) |
type |
Built-in syntax (email, url, number, …) |
min / max |
Bounds for number/date/range |
step |
Allowed increments |
minlength / maxlength |
Text length |
pattern |
Regex against value (anchored per rules) |
multiple |
Comma-separated emails, etc. |
Checking validity in JS
const form = document.querySelector("#signup");
const email = form.elements.email;
form.addEventListener("submit", (e) => {
if (!form.checkValidity()) {
e.preventDefault();
// show errors
}
});
email.addEventListener("blur", () => {
if (!email.validity.valid) {
showError(email, messageFor(email));
}
});
function messageFor(input) {
if (input.validity.valueMissing) return "This field is required.";
if (input.validity.typeMismatch) return "Enter a valid email.";
if (input.validity.tooShort) return `Use at least ${input.minLength} characters.`;
if (input.validity.patternMismatch) return input.title || "Invalid format.";
return "Invalid value.";
}
setCustomValidity("…") adds a custom error; clear with setCustomValidity("") when fixed.
CSS hooks
input:required {
/* subtle cue */
}
input:invalid {
/* avoid yelling on empty pristine fields — use :user-invalid where supported */
border-color: var(--danger);
}
input:user-invalid {
border-color: var(--danger);
}
:user-invalid / :user-valid reduce “red borders on page load” noise.
Server validation still required
Client attributes are UX, not security. Re-validate on the server every time. Attackers don’t use your HTML.
Accessible error messages
- Associate messages with
aria-describedbyoraria-errormessage(support varies). - Set
aria-invalid="true"when showing errors. - Move focus to the first invalid field on submit.
- Don’t rely only on color.
Interview out-loud
“Native constraint validation covers required, types, ranges, lengths, and patterns. I use checkValidity/validity states for custom messages, prefer :user-invalid styling, and always re-validate on the server. Accessible errors need text, associations, and focus management—not just red borders.”
Footguns
patternwithout a humantitle/custom message.type="number"still allowse/+quirks — know the validity model.- Disabling validation with
novalidateand forgetting to reimplement it. - Validating only on submit with no field-level feedback.
- Trusting client-only checks for auth or prices.
setCustomValidity async example
const username = document.querySelector("#username");
username.addEventListener("blur", async () => {
username.setCustomValidity("");
if (!username.validity.valid) return;
const taken = await fetch(`/api/username-taken?u=${encodeURIComponent(username.value)}`)
.then((r) => r.json())
.then((j) => j.taken);
if (taken) username.setCustomValidity("That username is taken.");
});
Clear custom validity before rechecking. Combine with reportValidity() on submit for UA bubbles, or suppress bubbles with novalidate and your own error UI that reuses validity flags.
Related
- Forms labels and inputs
- Accessible forms errors
- Autocomplete and name attributes
- fieldset and legend
Further reading
Related guides
- Autocomplete and Name Attributesname and autocomplete on form fields — password managers, autofill tokens, and why missing names break real users more than demos.
- fieldset and legendGroup related form controls with fieldset/legend — radio groups, disabled fieldsets, and accessible naming for control sets.
- Forms, Labels, and InputsWire labels to controls correctly, choose input types, group fields, and avoid the accessibility bugs that fail real users and audits.
- Accessibility Tree OverviewHow browsers build the accessibility tree from DOM and CSS — roles, names, states, what’s pruned, and how to inspect it in DevTools.
- Audio and Video ElementsNative audio/video — controls, sources, captions, autoplay policies, and accessibility requirements for media on the web.