HTML forms

HTML form validation, explained.

Browsers can check a form before it is sent, with no JavaScript at all: required fields, email and URL formats, lengths, number ranges and patterns. Here is every built-in check, how to change the messages and the styling, and the one thing browser validation can never do.

Updated 7 November 2026

The built-in checks

HTML
<form>
  <label for="name">Name</label>
  <input id="name" name="name" required minlength="2" />

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

  <label for="website">Website</label>
  <input id="website" type="url" name="website" placeholder="https://" />

  <label for="guests">Guests</label>
  <input id="guests" type="number" name="guests" min="1" max="12" />

  <label for="postcode">Postcode</label>
  <input id="postcode" name="postcode" pattern="[A-Za-z0-9 ]{3,10}" />

  <button>Send</button>
</form>
  • required: the field cannot be empty. On a checkbox it must be ticked; on a radio group, one option must be chosen.
  • type="email" and type="url": the value must be shaped like an email address or a full URL. See the email input for what that check misses.
  • minlength and maxlength: the number of characters. maxlength stops typing at the limit; minlength is checked on submit.
  • min, max and step: the range for numbers, dates and times.
  • pattern: a regular expression the whole value must match.

An empty optional field always passes: minlength, pattern and the type checks only apply once something has been typed. Add required as well if the field must be filled in.

What visitors see

When a check fails, the browser stops the submit, scrolls to the first invalid field, and shows a message next to it, such as Chrome’s “Please fill out this field.” for an empty required field. The wording and the look come from the browser and the visitor’s language, so they differ between Chrome, Firefox and Safari.

For a pattern, the title attribute is added to the message, so use it to describe the format. Better still, describe the format in visible text next to the field, so people get it right the first time.

Writing a pattern

The pattern must match the whole value, so [0-9]{4} means exactly four digits, with no need for ^ and $. Current browsers read it as a strict regular expression, so characters such as (, ) and - inside square brackets need a backslash. A pattern the browser cannot read is ignored, so the field accepts anything.

Keep patterns loose for names, addresses and phone numbers. A strict pattern turns away real people whose details do not look the way you expected.

Style invalid fields

:invalid matches an empty required field as soon as the page loads, so the whole form looks wrong before anyone has typed. :user-invalid waits until the visitor has interacted with the field or tried to submit, and works in current Chrome, Edge, Firefox and Safari.

CSS
/* Only after the visitor has interacted with the field */
input:user-invalid, textarea:user-invalid, select:user-invalid {
  border-color: #b42318;
}

/* Mark required fields in the label text, not only with colour */
.required-note { color: #6b6a80; font-weight: 400; }

Never rely on a red border alone. Say which fields are required in their labels, and keep the error message in text.

Your own error messages

setCustomValidity replaces the browser’s message, and also marks the field invalid until you clear it with an empty string. The validity object says which check failed:

JavaScript
const email = document.querySelector('#email');

email.addEventListener('input', () => {
  if (email.validity.valueMissing) {
    email.setCustomValidity('We need your email address to reply.');
  } else if (email.validity.typeMismatch) {
    email.setCustomValidity('That does not look like an email address.');
  } else {
    email.setCustomValidity(''); // valid again
  }
});

The browser also lets you check a field or the whole form yourself:

JavaScript
const form = document.querySelector('form');

form.checkValidity();   // true or false, shows nothing
form.reportValidity();  // the same, and shows the first error to the visitor

const field = document.querySelector('#guests');
field.validity.valid;          // true or false
field.validity.rangeOverflow;  // true when the value is above max
field.validationMessage;       // the message the browser would show

Turning validation off

novalidate on the form turns the browser checks off, while the validity object keeps working for your own script. On a single button, formnovalidate skips the checks only when that button is pressed, which suits a “save draft” button.

HTML
<!-- Turn the browser checks off, for example when your own script validates -->
<form novalidate>
  ...
</form>

Why browser validation is not enough

Every one of these checks runs in the visitor’s browser, so anything that does not use your page skips them: spam bots, scripts, or anyone who opens the developer tools and removes required. Browser validation helps honest visitors fix mistakes. Whatever receives the form must still treat every value as unchecked text.

A validated form that sends

This contact form posts to Heartdove, which saves every message, emails you an alert and tracks it until it is delivered or bounces. The browser checks the fields first. On arrival, Heartdove checks the access key and the website the form was sent from, refuses fields longer than 10,000 characters, catches bots with the hidden botcheck trap, and scores each message for spam. It stores your fields as text; it does not know which of them you made required, so keep those checks in the form.

contact.html
<form action="https://submit.heartdove.com/v1/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />

  <label for="name">Name</label>
  <input id="name" type="text" name="name" autocomplete="name" required minlength="2" maxlength="100" />

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

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="5" required minlength="10" maxlength="5000"></textarea>

  <input type="checkbox" name="botcheck" tabindex="-1" autocomplete="off" style="display:none" />
  <button type="submit">Send message</button>
</form>

Replace YOUR_ACCESS_KEY with your key from a free Heartdove form, or build the form in the contact form generator.

Questions

How do I make a field required in HTML?

Add the required attribute to the input, select or textarea. The browser will not send the form while that field is empty.

What does “Please fill out this field” mean?

It is the message Chrome shows when a required field is empty and someone tries to send the form. Fill in the highlighted field and send again.

Why is my pattern attribute not working?

The pattern must match the whole value, and current browsers ignore a pattern that is not a valid strict regular expression. Escape characters such as ( ) and - inside square brackets, and remember that an empty optional field always passes.

How do I change the browser’s validation message?

Call setCustomValidity with your message while the value is wrong, and with an empty string once it is fixed. For patterns, the title attribute is added to the browser’s message.

Is HTML validation secure?

No. It runs in the browser and can be skipped by anything that posts to your form directly. Validate again wherever the form is received.

Never lose another enquiry.

Connect your first form in a few minutes. Your existing design stays exactly as it is.