HTML Form Validation
1. Introduction
HTML5 built-in form validation lets browsers enforce rules on user input before any data is sent to the server โ with zero JavaScript required for basic cases. Attributes like required, minlength, pattern, and min/max define constraints, while the browser handles displaying error messages. This lesson covers all native validation attributes, how they work together, and how to write accessible custom error messages.
2. Concept
Validation Attributes Overview
| Attribute | Applies To | Validates |
|---|---|---|
required |
All inputs | Field must not be empty |
minlength / maxlength |
text, email, password, textarea | Character count |
min / max |
number, date, range, time | Numeric / date range |
pattern |
text, email, tel, url, search | Regular expression match |
type="email" |
input | Must contain @ and domain |
type="url" |
input | Must be valid URL with scheme |
aria-describedby on an input pointing to a hint paragraph. When validation fails, update that paragraph’s text with the error message so screen readers announce it.novalidate on a form without implementing JavaScript-based validation as a replacement. Without either, users can submit invalid or empty data to your server.3. Basic Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Demo</title>
</head>
<body>
<form action="/signup" method="post">
<!-- required: must not be empty -->
<label for="v-name">Full Name *</label>
<input type="text" id="v-name" name="name"
required
minlength="2"
maxlength="60"
>
<!-- email type + required -->
<label for="v-email">Email *</label>
<input type="email" id="v-email" name="email" required>
<!-- pattern: UK postcode -->
<label for="postcode">UK Postcode</label>
<input
type="text"
id="postcode"
name="postcode"
pattern="^[A-Z]{1,2}[0-9][0-9A-Z]?\s?[0-9][A-Z]{2}$"
title="Enter a valid UK postcode, e.g. SW1A 1AA"
>
<!-- Number range -->
<label for="v-age">Age (18โ100) *</label>
<input type="number" id="v-age" name="age" min="18" max="100" required>
<!-- Password with minlength -->
<label for="v-pass">Password (min 8 chars) *</label>
<input type="password" id="v-pass" name="password" minlength="8" required>
<button type="submit">Sign Up</button>
</form>
</body>
</html>
4. How It Works
Step 1 โ required Prevents Empty Submission
When a form is submitted, the browser checks every required field. If any is empty, the browser focuses the first invalid field and displays a tooltip error bubble. The form is not submitted until all constraints pass.
Step 2 โ minlength and maxlength on Text
minlength="2" prevents submitting with fewer than 2 characters. The browser shows an error message. maxlength hard-limits typing rather than showing an error on submit.
Step 3 โ pattern with a Regular Expression
The pattern attribute takes a regex without surrounding slashes. It must match the entire value. The title attribute provides the error message text the browser displays when the pattern does not match.
Step 4 โ min and max on Number Inputs
min="18" max="100" constrains the accepted value range. The browser rejects values outside this range on submit. The native number spinner also respects these bounds.
5. Real-World Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Checkout โ Shipping Details</title>
</head>
<body>
<h1>Shipping Information</h1>
<form action="/checkout/shipping" method="post" novalidate id="shipping-form">
<label for="s-name">Full Name</label>
<input type="text" id="s-name" name="full_name"
required minlength="2" maxlength="80"
autocomplete="name"
aria-required="true"
aria-describedby="s-name-err"
>
<span id="s-name-err" role="alert" aria-live="polite"></span>
<label for="s-address">Street Address</label>
<input type="text" id="s-address" name="address"
required minlength="5" autocomplete="street-address"
>
<label for="s-city">City</label>
<input type="text" id="s-city" name="city"
required autocomplete="address-level2"
>
<label for="s-postcode">Postcode</label>
<input type="text" id="s-postcode" name="postcode"
required
pattern="^[A-Z]{1,2}[0-9][0-9A-Z]?\s?[0-9][A-Z]{2}$"
title="Enter a valid UK postcode e.g. EC1A 1BB"
autocomplete="postal-code"
>
<label for="s-phone">Phone Number</label>
<input type="tel" id="s-phone" name="phone"
required
pattern="[+0-9\s\-]{7,20}"
title="Phone number (digits, spaces, hyphens, optional +)"
autocomplete="tel"
>
<button type="submit">Continue to Payment</button>
</form>
<script>
const form = document.getElementById('shipping-form');
form.addEventListener('submit', function(e) {
const nameInput = document.getElementById('s-name');
const errSpan = document.getElementById('s-name-err');
if (!nameInput.validity.valid) {
e.preventDefault();
errSpan.textContent = 'Please enter your full name (at least 2 characters).';
} else {
errSpan.textContent = '';
}
});
</script>
</body>
</html>
6. Common Mistakes
❌ Relying solely on client-side validation
<input type="email" required> <!-- only HTML validation -->
✓ Validate on both client AND server; never trust client data alone
<!-- HTML validates format first; server must also validate/sanitise -->
<input type="email" required>
❌ Missing title attribute with pattern
<input pattern="[A-Z]{3}">
✓ Always add title to explain the expected pattern
<input pattern="[A-Z]{3}" title="Enter exactly 3 uppercase letters">
7. Try It Yourself
8. Quick Reference
| Attribute | Valid On | Error Condition |
|---|---|---|
required |
Most inputs | Empty value on submit |
minlength |
text, email, password, textarea | Value shorter than n chars |
maxlength |
text, email, password, textarea | Prevents typing beyond n |
min |
number, date, time, range | Value below minimum |
max |
number, date, time, range | Value above maximum |
pattern |
text, tel, url, email, search | Value doesn’t match regex |
title |
All | Error tooltip text for pattern failures |