HTML Form Validation

Browsers can validate form input before it’s ever sent to a server, using nothing but HTML attributes. This won’t replace server-side validation (never trust data from the browser alone), but it gives users immediate feedback and stops obviously wrong submissions early.

required

Marks a field as mandatory. The browser blocks submission and shows a built-in message if it’s empty.

<label for="username">Username</label>
<input type="text" id="username" name="username" required />

min and max

For number and date-like inputs, min and max set the allowed range.

<label for="years">Years of experience</label>
<input type="number" id="years" name="years" min="0" max="50" />

<label for="event-date">Event date</label>
<input type="date" id="event-date" name="event-date" min="2026-01-01" max="2026-12-31" />

minlength and maxlength

For text inputs, minlength and maxlength restrict how many characters are allowed.

<label for="password">Password</label>
<input type="password" id="password" name="password" minlength="8" maxlength="64" required />

pattern

pattern takes a regular expression the value must match. Useful for formats that type alone doesn’t cover.

<label for="zip">ZIP code</label>
<input type="text" id="zip" name="zip" pattern="[0-9]{5}" title="Five digit ZIP code" />

Always pair pattern with a title attribute. Browsers show the title text as part of the validation message when the pattern doesn’t match, without it users just see a generic error with no idea what format is expected.

Type-based validation

Some type values validate their own format automatically, no extra attributes needed:

<input type="email" required />
<input type="url" required />

type="email" rejects a value with no @, type="url" requires something that looks like a valid URL.

Styling valid and invalid fields

CSS can target the validation state directly, so you can show feedback without any JavaScript:

input:invalid {
  border-color: #d33;
}

input:valid {
  border-color: #2a2;
}

/* Only show red after the user has interacted with the field */
input:invalid:not(:placeholder-shown) {
  border-color: #d33;
}

That last rule matters: styling every field red with :invalid before the user has typed anything makes a form look broken on page load. Combining it with :not(:placeholder-shown) waits until there’s a value to actually judge.

Custom validation messages

For messages more specific than the browser default, use the setCustomValidity method in JavaScript:

<input type="text" id="username" name="username" required />
const input = document.getElementById("username");

input.addEventListener("input", () => {
  if (input.value.includes(" ")) {
    input.setCustomValidity("Username cannot contain spaces");
  } else {
    input.setCustomValidity("");
  }
});

Calling setCustomValidity("") clears the error, this is required, or the field stays permanently invalid even after the user fixes it.

Client-side validation is not enough

Built-in HTML validation can always be bypassed, by disabling JavaScript, editing the HTML in dev tools, or sending a request directly to your server without a browser at all. Treat it as a UX improvement, not a security measure. Always validate and sanitise data again on the server.

Common mistakes

  • Relying on HTML validation alone and skipping server-side checks.
  • Using pattern without a title, leaving users with no idea what format is expected.
  • Styling :invalid fields red before the user has had a chance to type anything.

FAQ

Can I turn off browser validation for a form?

Yes, add novalidate to the <form> tag. Useful if you want full control over validation and messaging in JavaScript instead.

<form novalidate>...</form>

Does required work on checkboxes?

Yes. On a single checkbox, required means it must be checked, useful for “I agree to the terms” style confirmations.

  • Forms : labels, fieldsets, and general form structure
  • React Forms : handling validation and submission in a React app