On this page

Form validation with HTML5

12 min read TextCh. 4 — Forms

Native browser validation

HTML5 introduced a built-in browser validation system that works without JavaScript. When a user tries to submit a form with invalid data, the browser automatically displays error messages and blocks the submission.

This native validation has several advantages:

  • It works immediately without additional code
  • Messages are translated to the user's language
  • It is accessible for screen readers
  • It provides visual feedback with CSS pseudo-classes

Validation attributes

required

The most basic attribute. It indicates that the field is mandatory and cannot be left empty:

<label for="name">Name *</label>
<input type="text" id="name" name="name" required>

If the user tries to submit the form without filling in this field, the browser will display a message like "Please fill in this field."

minlength and maxlength

These control the allowed text length:

<label for="bio">Biography</label>
<textarea id="bio" name="bio"
  minlength="10"
  maxlength="500"
  placeholder="Minimum 10 characters, maximum 500"></textarea>

maxlength prevents the user from typing more characters than allowed. minlength shows an error on submit if the text is too short.

min, max, and step

For numeric and date fields:

<!-- Number between 1 and 100, in increments of 5 -->
<input type="number" name="quantity" min="1" max="100" step="5">

<!-- Minimum and maximum date -->
<input type="date" name="date" min="2025-01-01" max="2025-12-31">

<!-- Decimal numbers with step -->
<input type="number" name="price" min="0" max="9999.99" step="0.01">

The step attribute defines the valid increment. With step="0.01" you allow two decimal places.

pattern

Allows you to define a regular expression that the value must match:

<!-- Letters and spaces only -->
<input type="text" name="name" pattern="[a-zA-ZáéíóúñÁÉÍÓÚÑ\s]+"
  title="Only letters and spaces are allowed">

<!-- Zip code (5 digits) -->
<input type="text" name="zip" pattern="\d{5}"
  title="Enter a 5-digit zip code">

<!-- Phone number with format -->
<input type="tel" name="phone" pattern="\+?[0-9\s\-]{7,15}"
  title="Valid format: +1 555-0123">

The title attribute is important: its content is shown as part of the error message when the pattern does not match.

type as validator

HTML5 input types already include automatic validation:

Type Automatic validation
email Checks email format (must include @)
url Checks URL format (must include protocol)
number Only accepts numbers
date Only accepts valid dates
tel Does not validate format (varies by country), but shows numeric keyboard on mobile

The title attribute for custom messages

The title attribute is combined with pattern to provide descriptive error messages:

<input type="text" name="username"
  pattern="[a-zA-Z0-9_]{3,20}"
  title="Username: 3-20 characters, only letters, numbers, and underscore">

Without title, the browser will only show a generic message like "Please match the requested format." With title, the user receives clear instructions on what is expected.

CSS validation pseudo-classes

CSS provides pseudo-classes that apply based on the field's validation state:

/* Valid field */
input:valid {
  border-color: green;
}

/* Invalid field */
input:invalid {
  border-color: red;
}

/* Required field */
input:required {
  border-left: 3px solid orange;
}

/* Optional field */
input:optional {
  border-left: 3px solid gray;
}

/* Field within range (min/max) */
input:in-range {
  background-color: #e8f5e9;
}

/* Field out of range */
input:out-of-range {
  background-color: #ffebee;
}

Avoiding premature styles

A common problem is that :invalid applies even before the user interacts with the field. To avoid this, you can combine pseudo-classes:

/* Only show error after interaction */
input:not(:placeholder-shown):invalid {
  border-color: red;
}

/* Or use :user-invalid (modern support) */
input:user-invalid {
  border-color: red;
}

The :user-invalid pseudo-class is more recent and only activates after the user interacts with the field.

Disabling native validation

In some cases (for example, when using JavaScript validation), you can disable native validation:

<!-- Disable for the entire form -->
<form action="/submit" method="post" novalidate>
  <!-- Fields still have validation attributes,
       but the browser does not check them on submit -->
</form>

<!-- Disable only on the submit button -->
<button type="submit" formnovalidate>Save draft</button>

This is useful for "save draft" buttons that do not require all fields to be completed.

Complete example: form with validation

<form action="/contact" method="post">
  <label for="name">Full name *</label>
  <input type="text" id="name" name="name"
    required minlength="2" maxlength="100">

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

  <label for="subject">Subject *</label>
  <select id="subject" name="subject" required>
    <option value="">Choose a subject</option>
    <option value="support">Technical support</option>
    <option value="sales">Sales</option>
    <option value="other">Other</option>
  </select>

  <label for="message">Message *</label>
  <textarea id="message" name="message"
    required minlength="20" maxlength="2000"
    rows="6"></textarea>

  <button type="submit">Send message</button>
</form>

This form does not need a single line of JavaScript to validate that the fields are filled in, that the email has the correct format, and that the message meets the minimum length.

Practice

  1. Add validation to an existing form: Take a form you already have and add required, minlength, maxlength, min, max, and pattern to the appropriate fields. Verify that the browser blocks submission with invalid data.
  2. Create a field with a custom pattern: Build a field that only accepts a zip code from your country using pattern and provide a clear message with the title attribute.
  3. Style with validation pseudo-classes: Write CSS rules using :valid, :invalid, :required, and :out-of-range to give visual feedback on the form fields.

In the next lesson, we will learn about meta tags and how to optimize our pages for search engines.

Server-side validation is mandatory
HTML5 validation only works in the browser and can be easily disabled. ALWAYS validate data on the server as well. Client-side validation is a user experience enhancement, not a security measure.
CSS validation pseudo-classes
CSS offers pseudo-classes like :valid, :invalid, :required, and :placeholder-shown that let you style fields based on their validation state, without needing JavaScript.
html
<form action="/register" method="post" id="register-form">
  <div>
    <label for="username">Username *</label>
    <input type="text" id="username" name="username"
      required
      minlength="3"
      maxlength="20"
      pattern="[a-zA-Z0-9_]+"
      title="Letters, numbers, and underscores only. Between 3 and 20 characters."
      autocomplete="username">
  </div>

  <div>
    <label for="email">Email address *</label>
    <input type="email" id="email" name="email"
      required
      placeholder="[email protected]"
      autocomplete="email">
  </div>

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

  <div>
    <label for="website">Website (optional)</label>
    <input type="url" id="website" name="website"
      placeholder="https://yoursite.com">
  </div>

  <div>
    <label for="password">Password *</label>
    <input type="password" id="password" name="password"
      required
      minlength="8"
      pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
      title="Minimum 8 characters with at least one uppercase letter, one lowercase letter, and one number."
      autocomplete="new-password">
  </div>

  <button type="submit">Sign up</button>
</form>