JavaScriptForm Validation

Form Validation

Validating user input is one of the oldest jobs on the web. HTML5 gave us a surprisingly capable Constraint Validation API built right into the browser — required, pattern, type="email" and friends — backed by JavaScript hooks for custom rules and messages. Used well, you can ship robust client-side validation with very little code, and combine it with a server check for safety.

What the browser already does for free

Several HTML attributes turn into validation rules automatically. The browser refuses to submit a form whose fields violate them and shows a default bubble with the failing message.

  • required — field must not be empty.

  • type="email" / type="url" / type="number" / type="date" — value must parse as that type.

  • min, max, step — numeric / date bounds.

  • minlength, maxlength — string length bounds.

  • pattern="[A-Z]{3}\\d{3}" — custom regex (anchored, applied to the whole value).

signup.html

HTML
<form id="signup">
  <input name="email" type="email" required />
  <input name="age" type="number" min="13" max="120" required />
  <input name="username" pattern="[a-z0-9_]{3,16}" required />
  <button>Create account</button>
</form>
The validity object

Every form control exposes input.validity — a ValidityState object with a boolean for each kind of failure. The summary flag valid is true only if everything else is false.

JS
const email = form.elements.email;

email.validity.valueMissing;   // true if empty and required
email.validity.typeMismatch;   // true if type="email" and value is malformed
email.validity.patternMismatch;
email.validity.tooShort;
email.validity.tooLong;
email.validity.rangeUnderflow;
email.validity.rangeOverflow;
email.validity.stepMismatch;
email.validity.customError;    // set by setCustomValidity
email.validity.valid;          // overall verdict

The matching message lives at input.validationMessage and is localised by the browser.

Triggering validation from code

Two methods let you check validity programmatically:

  • form.checkValidity() / input.checkValidity() — returns boolean. Silent.

  • form.reportValidity() / input.reportValidity() — returns boolean and shows the native error bubble on the first invalid field.

JS
form.addEventListener("submit", (e) => {
  if (!form.checkValidity()) {
    e.preventDefault();
    form.reportValidity();          // surface the first error
    return;
  }
  // proceed: form is valid
});
Custom messages with setCustomValidity

Default messages are fine but generic. input.setCustomValidity(msg) overrides them. Setting an empty string clears the custom error and lets the built-in rules take over again.

custom message for a password field

JS
const pw = form.elements.password;

pw.addEventListener("input", () => {
  if (pw.value.length < 8) {
    pw.setCustomValidity("Use at least 8 characters.");
  } else if (!/[0-9]/.test(pw.value)) {
    pw.setCustomValidity("Include at least one number.");
  } else {
    pw.setCustomValidity("");        // clear — value is acceptable
  }
});
Remember to clear it
A non-empty custom message marks the field as invalid forever. If you set one based on a condition, remember to reset to `""` when the condition is no longer met, or your form will never submit again.
Cross-field validation

HTML attributes only know about one field at a time. For "passwords must match" and similar rules, drop into JavaScript on input events.

JS
const pw = form.password;
const confirm = form.confirmPassword;

function checkMatch() {
  if (confirm.value !== pw.value) {
    confirm.setCustomValidity("Passwords do not match.");
  } else {
    confirm.setCustomValidity("");
  }
}

pw.addEventListener("input", checkMatch);
confirm.addEventListener("input", checkMatch);
Styling invalid fields

CSS exposes the validation state through pseudo-classes — :valid, :invalid, :required, :optional, :user-invalid (modern). The last one is especially useful because it only applies after the user has interacted, avoiding the "everything is red on page load" look.

suppress :invalid styling until first submit

JS
form.addEventListener("submit", () => form.classList.add("was-submitted"));

paired CSS

JS
/*  .was-submitted :invalid { border-color: crimson; }  */
The invalid event

When a field fails validation during a submit, the browser fires an invalid event on it. You can listen for it to display custom UI instead of the native bubble.

JS
for (const field of form.elements) {
  field.addEventListener("invalid", (e) => {
    e.preventDefault();              // suppress the default bubble
    const msg = field.validationMessage;
    field.nextElementSibling.textContent = msg; // your inline error slot
  });
}
Opt out with novalidate

If you want to bypass HTML5 validation entirely — common for "save as draft" buttons or fully custom validation — add novalidate to the form.

HTML
<form novalidate>
  <!-- the browser will not block submission -->
</form>
Always validate on the server too
Client-side validation is a UX feature, not a security feature. A motivated user can disable JavaScript, edit attributes, or send the request directly. Server-side checks are the only ones that *enforce* anything.