frontend / html / 01_constraint_validation.md

Constraint Validation API and Senior Input Types

3 min read source

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() — returns true/false, fires an invalid event 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 where type="number" misbehaves).
  • pattern="[0-9]{4}" — a regex constraint feeding patternMismatch.
  • autocomplete="email" / "one-time-code" / "cc-number" — enables autofill and AT recognition (WCAG 1.3.5). Use standard tokens; don’t blanket autocomplete="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, allows e, scroll-changes the value; for codes/IDs use type="text" + inputmode + pattern.
  • Native error bubbles aren’t styleable and vary by browser — for branded/accessible errors, novalidate + custom messages.
  • setCustomValidity is 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-DD value; don’t parse the display.
  • :invalid styles 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 read validity flags for specific messages and setCustomValidity for cross-field rules.”
  • “I set novalidate and render my own accessible errors (aria-invalid + aria-describedby) for branded UX, while still using the validity engine.”
  • “Correct input types and inputmode give the right keyboard and pickers; autocomplete tokens enable autofill and satisfy 1.3.5.”
  • “Client validation is UX only — the server must re-validate.”

Cross-references

Further reading