On this page
Form validation with HTML5
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
- Add validation to an existing form: Take a form you already have and add
required,minlength,maxlength,min,max, andpatternto the appropriate fields. Verify that the browser blocks submission with invalid data. - Create a field with a custom pattern: Build a field that only accepts a zip code from your country using
patternand provide a clear message with thetitleattribute. - Style with validation pseudo-classes: Write CSS rules using
:valid,:invalid,:required, and:out-of-rangeto 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.
<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>
Sign in to track your progress