Skip to content
AgileLogics
7 min read

Form Validation

Browsers can check form input before it's submitted, with no JavaScript. Add constraints as attributes:

<form action="/register" method="post">
  <label for="user">Username (3–16 letters or digits)</label>
  <input id="user" name="user" required minlength="3" maxlength="16" pattern="[A-Za-z0-9]+">

  <label for="mail">Email</label>
  <input id="mail" name="mail" type="email" required>

  <label for="age">Age</label>
  <input id="age" name="age" type="number" min="13" max="120" step="1">

  <label for="start">Start date</label>
  <input id="start" name="start" type="date" min="2026-01-01">

  <label><input type="checkbox" name="terms" required> I accept the terms</label>

  <button>Create account</button>
</form>
Attribute Constraint
required Must not be empty (checkbox: must be ticked; radio group: one chosen)
minlength / maxlength Text length limits (maxlength also stops typing)
min / max / step Range and granularity for numbers, dates, times, and ranges
type="email" / type="url" Value must look like an email / absolute URL
pattern Value must match a regular expression — the whole value

When someone submits with a problem, the browser cancels the submission, focuses the first invalid field, and shows a message in the visitor's language.

pattern

pattern is a JavaScript regular expression that must match the entire value (it's implicitly anchored with ^(?: ... )$), and it only applies when the field isn't empty. Explain the format in the label or a hint, because the browser's default message just says the format is wrong. Test patterns with our Regex Tester.

Checking validity from JavaScript

Each control has a validity object you can inspect. This page fills in some bad values and prints what the browser thinks:

<form>
  <input id="user" required minlength="3" pattern="[A-Za-z0-9]+" aria-label="Username">
  <input id="age" type="number" min="13" max="120" aria-label="Age">
  <input id="mail" type="email" aria-label="Email">
</form>
<script>
  const user = document.getElementById("user");
  const age = document.getElementById("age");
  const mail = document.getElementById("mail");

  console.log("empty user valid?", user.checkValidity(), user.validity.valueMissing);
  user.value = "ada lovelace";
  console.log("pattern mismatch?", user.validity.patternMismatch);
  age.value = "7";
  console.log("too small?", age.validity.rangeUnderflow);
  mail.value = "ada@example";
  console.log("email valid?", mail.validity.valid);
  mail.value = "not-an-email";
  console.log("email valid now?", mail.validity.valid, mail.validity.typeMismatch);
</script>
empty user valid? false true
pattern mismatch? true
too small? true
email valid? true
email valid now? false true

Note that ada@example counts as a valid email — the check is loose on purpose (addresses on internal networks don't need a dot). Only sending a confirmation email proves an address works.

Styling valid and invalid fields

CSS can style fields by state: :invalid, :valid, :required, and :user-invalid (invalid after the visitor has interacted with the field, so an empty form isn't red on page load). See CSS for selectors.

Turning validation off

novalidate on the form (or formnovalidate on one button, like "Save draft") skips built-in validation — useful when JavaScript shows its own messages.

Client-side validation is not security

Anyone can edit your HTML in developer tools or send requests without a browser. Always validate again on the server. Browser validation is for a better experience, not for protection.

Practice

Write an input for a UK-style product code: exactly three capital letters, a hyphen, and four digits (like ABC-1234), required, with a hint in the label.

Show solution
<label for="code">Product code (for example ABC-1234)</label>
<input id="code" name="code" required pattern="[A-Z]{3}-[0-9]{4}">

Related developer tools