skip to content

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%

answer

  1. one is silent, one talks
  2. both answer the same question
  3. asking has a side effect
  4. the event that lets you draw your own message
  5. neither one sends the form

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.

solid answer

~50 s

They answer the same question and differ only in whether the user is told. `checkValidity()` returns `true` when the element satisfies its constraints, `false` otherwise, and fires an `invalid` event at each failing control — but it displays nothing. `reportValidity()` does exactly the same and then *reports*: it shows the browser's native message bubble on the first invalid control and moves focus to it. Both exist on individual controls and on the `<form>` itself, where they validate every submittable control in it. Neither submits anything. So I use `checkValidity()` when I want to decide something quietly — enabling a button, deciding whether to run an async check — and `reportValidity()` when the user has just asked to proceed and deserves to see what is wrong. If I am rendering my own error UI I use `checkValidity()` plus the `validity` object, because `reportValidity()` would pop a native bubble on top of my own messages.

code

javascript · 12 lines
javascript
const form = document.querySelector('#signup');

// Silent: decide something without telling the user.
const ok = form.checkValidity();
document.querySelector('#continue').disabled = !ok;

// Loud: the user asked to proceed, so show the browser's own messages.
document.querySelector('#continue').addEventListener('click', () => {
  if (form.reportValidity()) {
    form.requestSubmit(); // reportValidity itself never submits
  }
});

go deeper

for a junior

Remember the pair by their names: check is silent and returns a boolean, report does the same and also shows the browser's message on the first bad field.

for a middle

Explain that both fire invalid events at failing controls, both work on a single control or on the whole form, and neither submits anything.

for a senior

Show the production choice: report is a fine default for internal tools, but a design system takes over with checkValidity plus the validity flags so messages are inline, styled, and in the page's language.

for a principal

Be ready to set the house rule for which layer owns validation feedback, so teams do not mix native bubbles with custom inline errors in the same product and leave users with two competing error surfaces.

## The two calls Constraint validation exposes two methods on every form-associated control and on `<form>`: - `checkValidity()` — runs the constraints, fires an `invalid` event at each control that fails, returns `false` if anything failed and `true` otherwise. No UI. - `reportValidity()` — does all of the above and then reports the first failure to the user: the browser's own message bubble appears next to the control, and focus (and scroll) moves there. The naming is the mnemonic: *check* is silent, *report* talks. ```js const form = document.querySelector('#signup'); const email = form.elements.email; email.checkValidity(); // true/false, nothing visible form.reportValidity(); // same answer, plus a bubble on the first bad field ``` ## What both of them do that surprises people They are not pure predicates. Both dispatch an `invalid` event at every control that fails, so a listener runs as a side effect of merely asking. That event does not bubble, so listeners go on the controls themselves (or use capture on an ancestor). This is exactly what makes a per-field pattern possible: listen for `invalid` on each input, and in that listener render your own message. ```js for (const el of form.elements) { el.addEventListener('invalid', (event) => { event.preventDefault(); // suppress the native bubble showMyError(el, el.validationMessage); }); } ``` Canceling the `invalid` event suppresses the browser's default report for that control, which is one way to keep the API's validation while owning the presentation. ## Form-level versus control-level Called on a `<form>`, either method walks every submittable control in the form and aggregates the result: `false` if any single one fails. Called on one control it considers only that control. Controls that do not participate — `disabled`, `readonly`, `type="hidden"`, buttons, anything inside a `<datalist>` — are skipped; their `willValidate` property is `false` and their `checkValidity()` returns `true` because there is nothing to violate. ## Neither one submits A frequent bug is expecting `reportValidity()` to submit when everything passes. It does not. It is purely a validation call. In a custom submit flow you write both halves: ```js form.addEventListener('submit', (event) => { if (!form.checkValidity()) { event.preventDefault(); renderErrors(form); } }); ``` And note the subtlety in that snippet: with a normal form the browser has *already* validated before dispatching `submit`, so this handler only ever sees a valid form. The pattern is written that way for forms carrying `novalidate`, where the browser's automatic pass is switched off but the API keeps working exactly as before. ## Choosing between them Use `checkValidity()` when the answer feeds a decision rather than the user: deciding whether to enable a button, whether to send an availability request, whether to advance a wizard step. Use `reportValidity()` when the user has just tried to proceed and you want the browser's built-in messaging without writing any UI — it is a genuinely good default for internal tools and prototypes, because it is free, localized by the browser, and focuses the offending control. Use neither for presentation if you are building a design system: there `checkValidity()` plus the `validity` flags and `validationMessage` string give you the data, and your own markup gives you the message, which you can style, place inline, and reference from the field. `reportValidity()` would fight that by popping a native bubble the moment you called it. ## Reading the message text Alongside the methods, every control exposes `validationMessage`: the string the browser *would* display, in the browser's UI language, or an empty string when the control is valid. It is useful as a fallback, but its locale follows the browser rather than the page, which is a common reason teams write their own copy instead. ## What weak answers sound like "`checkValidity()` checks one field and `reportValidity()` checks the whole form" — no, both work at either scope. "`reportValidity()` submits if valid" — it never submits. "Calling `checkValidity()` has no side effects" — it fires `invalid` events, which is precisely how the per-field error pattern is built.

  • Does calling checkValidity() have any side effect at all?
    Yes — it fires an `invalid` event at every control that fails. The event does not bubble, so listeners attach per control or in the capture phase. This is the hook the per-field error pattern uses: listen for `invalid`, call `preventDefault()` to kill the native bubble, and render your own message from `validationMessage` or the `validity` flags.
  • If everything passes, does reportValidity() submit the form?
    No. It only validates and reports; it never navigates or sends anything. If you want submission you call `form.requestSubmit()`, which runs the normal submit path including validation and fires the `submit` event — unlike `form.submit()`, which bypasses both.
  • Why might a design-system form deliberately avoid reportValidity()?
    Because it pops the browser's native bubble, which cannot be styled or positioned, appears one at a time, is written in the browser's UI language rather than the page's, and is not in the DOM so it cannot be referenced from the field. A custom UI uses `checkValidity()` for the answer and renders its own inline message instead.

saying these in an interview costs you the question

  • checkValidity is for one field, reportValidity for the form
  • reportValidity submits the form when everything passes
  • checkValidity is side-effect free
  • The invalid event bubbles so you can delegate on the form
  • reportValidity messages can be styled with CSS

context