Constraint Validation API and Senior Input Types
The browser validates forms natively via the Constraint Validation API: required, type, min/max, pattern, minlength, etc. produce a ValidityState per field, surface :valid/:invalid CSS, and block submission. You read/override it in JS with checkValidity(), reportValidity(), and setCustomValidity(). Senior input types (email, url, tel, date, color, file multiple, search) plus inputmode and autocomplete give correct keyboards, pickers, and autofill for free. Pair this with accessible error messaging — see Accessible Forms.
What is the Constraint Validation API?
The built-in form-validation system. Declarative attributes (required, pattern, min, max, minlength, step, type) define constraints; the browser computes a validity object on each control and prevents submit if invalid. You don’t need a library for basic validation.
<input type="email" required minlength="5" />What’s on ValidityState?
Boolean flags explaining why a field is invalid: valueMissing (required + empty), typeMismatch (bad email/url), patternMismatch, tooLong/tooShort, rangeUnderflow/rangeOverflow, stepMismatch, badInput, and valid (all good). Branch on these to show specific messages:
if (input.validity.valueMissing) show("This field is required.");
else if (input.validity.typeMismatch) show("Enter a valid email.");checkValidity() vs reportValidity() vs setCustomValidity()?
checkValidity()— returnstrue/false, fires aninvalidevent on failing fields, shows nothing.reportValidity()— same check and displays the browser’s native error bubble.setCustomValidity(msg)— sets a custom error (non-empty = invalid); set it back to""to clear. Used for cross-field rules (password confirmation):
confirm.setCustomValidity(confirm.value !== pw.value ? "Passwords don't match" : "");How do you build real-time validation without a library?
Validate on input/blur, suppress native bubbles with novalidate on the form, and render your own accessible messages:
form.setAttribute("novalidate", "");
field.addEventListener("blur", () => {
field.setCustomValidity(""); // reset
if (!field.checkValidity()) {
field.setAttribute("aria-invalid", "true"); // a11y
errorEl.textContent = messageFor(field.validity);
} else { field.removeAttribute("aria-invalid"); errorEl.textContent = ""; }
});novalidate keeps native submit-blocking off so you control UX, while still using the validity engine. Wire aria-invalid + aria-describedby per Accessible Forms.
Which input types matter and what do they give you?
| Type | Benefit |
|---|---|
email / url / tel |
type validation + the right mobile keyboard |
number / range |
numeric input, min/max/step |
date / time / datetime-local / month |
native pickers, locale-aware |
color |
native color picker |
search |
clear button, search semantics |
file (+ multiple, accept, capture) |
file picker, type filter, camera on mobile |
password |
masking + autofill/manager integration |
inputmode, pattern, autocomplete — what do they add?
inputmode="numeric"/"decimal"/"tel"— picks the on-screen keyboard without changing the control type or validation (great for OTP/PIN wheretype="number"misbehaves).pattern="[0-9]{4}"— a regex constraint feedingpatternMismatch.autocomplete="email"/"one-time-code"/"cc-number"— enables autofill and AT recognition (WCAG 1.3.5). Use standard tokens; don’t blanketautocomplete="off".
Gotchas
- Client validation is UX, not security — always re-validate on the server; constraints are trivially bypassed.
type="number"quirks — strips leading zeros, allowse, scroll-changes the value; for codes/IDs usetype="text"+inputmode+pattern.- Native error bubbles aren’t styleable and vary by browser — for branded/accessible errors,
novalidate+ custom messages. setCustomValidityis sticky — forgetting to reset it to""leaves the field permanently invalid.- Date input formatting is locale/browser-dependent — the displayed format differs from the submitted
YYYY-MM-DDvalue; don’t parse the display. :invalidstyles on load can flash before interaction — scope with:user-invalid(newer) or only style after blur/submit.
Related
Further reading
- MDN — Constraint validation: https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation
- MDN —
ValidityState: https://developer.mozilla.org/en-US/docs/Web/API/ValidityState - MDN —
<input>types: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
Interview angle 5
- “How much form validation can you do without a library?” - most of it.
Attributes like
required,type,patternandmin/maxproduce aValidityStateper control, and you branch on its flags -valueMissing,typeMismatch,patternMismatch- to show a specific message rather than a generic one.- Follow-up: “How do you handle a rule that spans two fields?” -
setCustomValidity, which is the escape hatch for anything the attributes cannot express, such as a password confirmation. Remember to reset it to"", because it is sticky and a stale message leaves the field permanently invalid.
- Follow-up: “How do you handle a rule that spans two fields?” -
- “Why would you add
novalidateand then still use the API?” - because the native error bubbles cannot be styled and differ between browsers, but the validity engine is still worth having.novalidateturns off the browser’s UI and its submit blocking, and you render your own messages witharia-invalidandaria-describedbyso screen readers get them too. - “What do the newer input types actually buy you?” - the right on-screen
keyboard, native pickers for dates and colours, and type validation for free.
inputmodegives you the keyboard without changing the control type, which matters for codes and PINs wheretype="number"strips leading zeros and acceptse. - “Why does
autocompletematter beyond convenience?” - standard tokens let password managers and assistive technology identify the field’s purpose, which is a WCAG 1.3.5 requirement. A blanketautocomplete="off"breaks that and usually breaks the user’s password manager as well. - “Is any of this security?” - none of it. Every constraint is client-side and trivially bypassed with a direct request, so the server must re-validate everything. Client validation is a UX feature that happens to look like a gate.