skip to content

Constraint Validation API

Validation attributes plus the JavaScript API that reads and overrides them: ValidityState, checkValidity, and setCustomValidity. Interviewers want you to say out loud that client validation is UX, never a security control.

part ofHTMLoverview, primer and where to startread it →
on this pageshow

questions

6

In a plain HTML form, one input carries required and another carries pattern="[0-9]{4}". What exactly does the browser do when the user presses the submit button while those constraints are not satisfied?

level: juniorimportance: must knowfreq 76%

answer

  1. nothing reaches the network
  2. the handler you wrote never runs
  3. one control gets focus, others do not
  4. invalid fires per control, no bubbling
  5. empty value satisfies pattern

basics

~20 s

The browser blocks submission: no submit event fires and no request goes out. It focuses the first invalid control, shows a built-in message bubble beside it, and fires a non-bubbling invalid event on each failing control.

solid answer

~50 s

Before submitting, the browser runs interactive constraint validation over the form's submittable controls. If any control fails, submission is aborted — the form's `submit` event never fires, so a handler that calls `preventDefault()` is never reached and nothing is sent. The browser fires an `invalid` event at each failing control (it does not bubble, and it is cancelable), then reports the first one: it focuses and scrolls to that control and shows the browser's own message bubble, such as "Please fill out this field" for `required` or "Please match the requested format" for `pattern`. Two details worth saying out loud: `pattern` is matched against the whole value, so an empty field does not fail it — you need `required` for that — and all of this is a usability affordance only. Anyone can send the request without a browser, so the server still validates everything.

go deeper

for a junior

Be able to say that required and pattern stop the submission entirely — no request, no submit event — and that the browser picks the first bad field, focuses it, and shows its own message.

for a middle

Explain the ordering: validation runs before submit, invalid fires per control and does not bubble, and pattern is anchored to the whole value so an empty field passes it.

for a senior

Show you have debugged this in production: a hidden or off-screen invalid control makes a form dead-silent, and the native message is unstyleable and in the browser's locale, which is what pushes teams to take the reporting over.

for a principal

Own the rule that constraint attributes are a funnel for honest mistakes and never a trust boundary; make sure every field the markup constrains is independently enforced server-side and that the two rule sets cannot drift.

## What the attributes actually assert HTML's constraint validation is declarative. Attributes on a control state a condition the value must meet, and the browser enforces them for free: - `required` — the control must have a value; an empty one fails. - `pattern` — the value must match a regular expression. It is matched against the **entire** value, so `^` and `$` are unnecessary; adding them is harmless but reveals a misunderstanding. An empty value never fails `pattern`. - `minlength` / `maxlength` — bounds on the number of characters. `maxlength` mostly acts as a hard cap while typing, so users usually never see an error from it. - `min` / `max` / `step` — bounds and granularity for numeric and date-like controls. The control's `type` also contributes a constraint of its own: `type="email"` and `type="url"` reject values that are not shaped like an address or a URL. ```html <form action="/pay" method="post"> <label for="pin">PIN</label> <input id="pin" name="pin" required pattern="[0-9]{4}" title="Four digits" inputmode="numeric"> <button>Pay</button> </form> ``` ## What happens at submit time When the user activates a submit button, the browser first runs **interactive validation** over the form's submittable controls. If every one of them is satisfied, submission proceeds normally: the `submit` event fires, and unless a script cancels it the request goes out. If any control fails, the sequence is different and this is the part interviewers listen for: 1. Submission is aborted. **The `submit` event does not fire at all.** A script that hangs its logic off `submit` never runs, which is why a form can appear to "do nothing" when a hidden or off-screen control is invalid. 2. An `invalid` event fires at each failing control. It does **not** bubble, so you cannot delegate it on the `<form>` — you attach listeners per control, or use the capture phase. It is cancelable; canceling it suppresses the browser's default report for that control. 3. The browser reports the problem for the first invalid control: it focuses and scrolls to it and displays a native message bubble anchored to it. The text in that bubble comes from the browser, in the **browser's** UI language, and it is not part of the DOM — you cannot style it, select it, or point `aria-describedby` at it. For `pattern` failures browsers surface the control's `title` as additional hint text, which is the only lever the markup gives you over the wording. ## Which controls take part Not every element is validated. A control is skipped when it is `disabled`, `readonly`, `type="hidden"`, a button-ish type, or sits inside a `<datalist>`. The read-only DOM property `willValidate` tells you whether a given element participates at all — a useful thing to check when a constraint appears to be ignored. You can also opt out deliberately. `novalidate` on the `<form>` turns off this automatic validate-and-report step entirely, and `formnovalidate` on a specific submit button turns it off for submissions triggered by that button — the standard way to build a "Save draft" button next to a "Publish" button that does enforce the rules. ```html <button>Publish</button> <button formnovalidate name="action" value="draft">Save draft</button> ``` Importantly, neither attribute disables the constraints themselves. The control still computes its validity, still matches `:invalid`, and `checkValidity()` still reports the failure. Only the browser's automatic UI is suppressed. ## Why it is UX and never a control Everything above happens inside one browser tab. A request built with a command-line client, a mobile app, or a modified page carries none of it. Constraint validation exists to give a user immediate, local feedback instead of a round trip — it narrows the funnel of accidental mistakes. It says nothing about what the server may trust, and the server must re-check every field on its own terms. ## The common misreads Candidates often assume the `submit` event fires and that they can inspect the failure inside it; it does not fire when validation fails. They often assume `pattern` implies `required`; it does not. And they often assume `maxlength` produces an error message; usually it simply stops the keystrokes.

  • Does pattern reject an empty field?
    No. A control with an empty value is never patternMismatch — the constraint only applies once there is something to match. If the field is mandatory you must add `required` alongside `pattern`. Note also that the expression is matched against the whole value, so anchoring it with `^…$` adds nothing.
  • How would you add a "Save draft" button that skips validation while "Publish" still enforces it?
    Put `formnovalidate` on the draft button. It suppresses the browser's validate-and-report step for submissions triggered by that button only, so the draft posts even with empty required fields, while the publish button still blocks. `novalidate` on the `<form>` would disable it for every button, which is not what you want here.
  • A required input is failing validation but the user sees no message and the form silently does nothing. What would you check?
    Usually the invalid control is not visible — collapsed in a hidden panel, scrolled off, or `display:none`. The browser aborts submission and tries to report on a control it cannot show. Check `form.checkValidity()` and iterate the controls to find which one is invalid, and reveal it before the browser has to report on it.

saying these in an interview costs you the question

  • Client validation makes server-side checks unnecessary
  • The submit event fires and you inspect validity inside it
  • pattern needs ^ and $ or it matches substrings
  • invalid bubbles, so delegate it on the form
  • pattern alone makes a field mandatory

context

open as a page

HTML form controls expose both checkValidity() and reportValidity(). What does each one do, what do they have in common, and when would you call one over the other?

level: middleimportance: must knowfreq 58%

basics

~10 s

Both return a boolean and fire an invalid event at every failing control. checkValidity() does it silently; reportValidity() additionally shows the browser's message and focuses the first invalid control. Neither one submits the form.

open as a page

Using setCustomValidity() on an HTML form control, how do you make the browser show your own error text — for example "passwords do not match" — and what breaks if you get one step wrong?

level: middleimportance: must knowfreq 52%

basics

~20 s

Call setCustomValidity("message") to mark a control invalid with your own text; the browser then blocks submission and shows that message. You must call setCustomValidity("") once the value becomes acceptable, or the control stays invalid forever.

open as a page

An HTML form control exposes a validity property returning a ValidityState object. Which flags does it carry, and how do you use them to tell why a field is invalid rather than just that it is?

level: middleimportance: should knowfreq 45%

basics

~20 s

ValidityState exposes one boolean per failure reason — valueMissing, typeMismatch, patternMismatch, tooShort, tooLong, rangeUnderflow, rangeOverflow, stepMismatch, badInput, customError — plus valid. Test the specific flag to choose the right message instead of inferring it from the value.

open as a page

On a freshly loaded page, an empty <input required> already matches the CSS :invalid pseudo-class before the user has typed anything. Why does that happen, and what does :user-invalid do differently?

level: seniorimportance: should knowfreq 40%

basics

~20 s

:invalid reflects constraint state alone, so an untouched empty required field already matches it at page load. :user-invalid matches only after the user has edited the field or attempted submission, which is why error state should key off it.

open as a page

A design system needs inline, styled, consistently worded error messages on every form. How would you decide between keeping the browser's native validation bubbles and switching them off to render your own, and what must you reproduce if you take it over?

level: principalimportance: should knowfreq 33%

basics

~20 s

Native bubbles are free but unstyleable, shown one at a time, dismissed on blur, worded in the browser's language, and absent from the DOM. Taking over means adding novalidate, keeping the constraint attributes, and reproducing the message, the focus move and the announcement yourself.

open as a page