Constraint Validation API and Senior Input Types
TL;DR
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 ../16_accessibility/05_forms_accessibility.md.
Interview Q&A
Q: What is the Constraint Validation API?
A: 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" />
Q: What’s on ValidityState?
A: 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.");
Q: checkValidity() vs reportValidity() vs setCustomValidity()?
A:
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" : "");
Q: How do you build real-time validation without a library?
A: 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 ../16_accessibility/05_forms_accessibility.md.
Q: Which input types matter and what do they give you?
A:
| 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 |
Q: inputmode, pattern, autocomplete — what do they add?
A:
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 / edge cases
- 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.
What a senior is expected to say
- “The Constraint Validation API gives native validation via attributes +
ValidityState; I readvalidityflags for specific messages andsetCustomValidityfor cross-field rules.” - “I set
novalidateand render my own accessible errors (aria-invalid+aria-describedby) for branded UX, while still using the validity engine.” - “Correct input types and
inputmodegive the right keyboard and pickers;autocompletetokens enable autofill and satisfy 1.3.5.” - “Client validation is UX only — the server must re-validate.”
Cross-references
- Accessible error messaging and labels: ../16_accessibility/05_forms_accessibility.md
- Responsive images (another native HTML capability): 02_responsive_images.md
- Backend validation (the real gate): ../../backend/07_rest_apis/
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