· Forms
HTML Form Validation Techniques
Implement robust client-side validation for better user experience.
Form validation ensures users enter data correctly before submission. Good validation provides immediate feedback, prevents errors, and creates a frustration-free experience. The best part? HTML5 provides powerful built-in validation that works without JavaScript. In this guide, you’ll learn how to implement effective form validation using native HTML attributes and enhance it with custom validation when needed.
Client-side validation catches errors early, but always remember to validate on the server too—never trust client-side validation alone for security.
Built-in HTML5 Validation
HTML5 gives you validation superpowers through simple attributes. No JavaScript required!
The required Attribute
The most basic validation—ensure a field isn’t empty:
<label for="name">Full Name (required)</label>
<input id="name" type="text" name="name" required>
<label for="email">Email (required)</label>
<input id="email" type="email" name="email" required>
<button type="submit">Submit</button>When users try to submit without filling required fields, the browser shows a validation message automatically.
Input Types with Built-in Validation
Different input types provide automatic format validation:
<!-- Email validation -->
<input type="email" name="email" required>
<!-- Must contain @ symbol and valid email format -->
<!-- URL validation -->
<input type="url" name="website">
<!-- Must start with http:// or https:// -->
<!-- Number validation -->
<input type="number" name="age" min="18" max="120">
<!-- Must be a number between 18 and 120 -->
<!-- Date validation -->
<input type="date" name="birthdate" min="1900-01-01" max="2010-12-31">
<!-- Must be a valid date in range -->
<!-- Tel (no automatic validation, but shows number keyboard on mobile) -->
<input type="tel" name="phone" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}">Pattern Matching with RegEx
Use the pattern attribute for custom validation with regular expressions:
<!-- US Phone number: 555-123-4567 -->
<label for="phone">Phone (###-###-####)</label>
<input
id="phone"
type="tel"
name="phone"
pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
title="Enter phone number in format: 555-123-4567"
required
>
<!-- Username: 3-16 characters, letters and numbers only -->
<label for="username">Username</label>
<input
id="username"
type="text"
name="username"
pattern="[A-Za-z0-9]{3,16}"
title="Username must be 3-16 characters, letters and numbers only"
required
>
<!-- Postal code: 5 digits or 5+4 format -->
<label for="zip">ZIP Code</label>
<input
id="zip"
type="text"
name="zip"
pattern="[0-9]{5}(-[0-9]{4})?"
title="Enter 5-digit ZIP code (optional +4: 12345-6789)"
>Pro tip: Always include a title attribute with pattern—browsers use it to explain what format is expected.
Min, Max, and Length Constraints
Control the range and length of user input:
<!-- Text length -->
<label for="username">Username (3-20 characters)</label>
<input
id="username"
type="text"
name="username"
minlength="3"
maxlength="20"
required
>
<!-- Number range -->
<label for="age">Age (18-100)</label>
<input
id="age"
type="number"
name="age"
min="18"
max="100"
required
>
<!-- Date range -->
<label for="appointment">Appointment Date</label>
<input
id="appointment"
type="date"
name="appointment"
min="2024-01-01"
max="2024-12-31"
required
>
<!-- Step increments for numbers -->
<label for="price">Price (increments of $0.50)</label>
<input
id="price"
type="number"
name="price"
min="0"
step="0.50"
>Custom Validation Messages
Replace default browser messages with your own:
<form id="signup-form">
<label for="email">Email Address</label>
<input id="email" type="email" name="email" required>
<button type="submit">Sign Up</button>
</form>
<script>
const emailInput = document.getElementById('email');
emailInput.addEventListener('invalid', (event) => {
if (emailInput.validity.valueMissing) {
emailInput.setCustomValidity('Please enter your email address');
} else if (emailInput.validity.typeMismatch) {
emailInput.setCustomValidity('Please enter a valid email (e.g., [email protected])');
}
});
// Clear custom message when user types
emailInput.addEventListener('input', () => {
emailInput.setCustomValidity('');
});
</script>Real-Time Validation Feedback
Show validation errors as users type:
<style>
input:invalid {
border-color: #dc3545;
}
input:valid {
border-color: #28a745;
}
input:focus:invalid {
outline-color: #dc3545;
}
.error-message {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
display: none;
}
input:invalid + .error-message {
display: block;
}
</style>
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
minlength="8"
required
>
<div class="error-message">
Password must be at least 8 characters long
</div>Password Confirmation
Ensure passwords match with JavaScript:
<form id="password-form">
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
minlength="8"
required
>
<label for="confirm">Confirm Password</label>
<input
id="confirm"
type="password"
name="confirm"
minlength="8"
required
>
<button type="submit">Submit</button>
</form>
<script>
const password = document.getElementById('password');
const confirm = document.getElementById('confirm');
function validatePassword() {
if (password.value !== confirm.value) {
confirm.setCustomValidity('Passwords do not match');
} else {
confirm.setCustomValidity('');
}
}
password.addEventListener('change', validatePassword);
confirm.addEventListener('input', validatePassword);
</script>Form-Level Validation
Validate the entire form before submission:
<form id="registration-form" novalidate>
<label for="username">Username</label>
<input id="username" type="text" name="username" required>
<span class="error" data-field="username"></span>
<label for="email">Email</label>
<input id="email" type="email" name="email" required>
<span class="error" data-field="email"></span>
<label for="age">Age</label>
<input id="age" type="number" name="age" min="18" required>
<span class="error" data-field="age"></span>
<button type="submit">Register</button>
</form>
<script>
const form = document.getElementById('registration-form');
form.addEventListener('submit', (event) => {
// Prevent submission if invalid
if (!form.checkValidity()) {
event.preventDefault();
// Show errors for each invalid field
const inputs = form.querySelectorAll('input');
inputs.forEach(input => {
const errorSpan = form.querySelector(`[data-field="${input.name}"]`);
if (!input.validity.valid) {
errorSpan.textContent = input.validationMessage;
input.setAttribute('aria-invalid', 'true');
} else {
errorSpan.textContent = '';
input.setAttribute('aria-invalid', 'false');
}
});
}
});
</script>Accessible Validation
Make validation accessible to all users:
<form>
<div class="form-group">
<label for="email">
Email Address <span aria-label="required">*</span>
</label>
<input
id="email"
type="email"
name="email"
aria-required="true"
aria-describedby="email-error email-help"
required
>
<div id="email-help" class="help-text">
We'll never share your email with anyone
</div>
<div id="email-error" class="error" role="alert" aria-live="polite">
<!-- Error message appears here -->
</div>
</div>
</form>Key accessibility features:
- Use
aria-requiredin addition torequired - Connect error messages with
aria-describedby - Use
role="alert"for error messages - Use
aria-live="polite"for dynamic messages - Mark invalid fields with
aria-invalid="true"
Complete Validation Example
Here’s a full form with comprehensive validation:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form Validation Example</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: system-ui, sans-serif;
max-width: 500px;
margin: 2rem auto;
padding: 0 1rem;
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
input {
width: 100%;
padding: 0.5rem;
font-size: 1rem;
border: 2px solid #ddd;
border-radius: 4px;
}
input:focus {
outline: none;
border-color: #0066cc;
}
input:invalid:not(:placeholder-shown) {
border-color: #dc3545;
}
input:valid:not(:placeholder-shown) {
border-color: #28a745;
}
.error {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
display: none;
}
input:invalid:not(:placeholder-shown) + .error {
display: block;
}
button {
background: #0066cc;
color: white;
padding: 0.75rem 2rem;
font-size: 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0052a3;
}
.required {
color: #dc3545;
}
</style>
</head>
<body>
<h1>Create Account</h1>
<form id="signup-form">
<div class="form-group">
<label for="username">
Username <span class="required">*</span>
</label>
<input
id="username"
type="text"
name="username"
pattern="[A-Za-z0-9_]{3,20}"
placeholder="john_doe"
title="3-20 characters, letters, numbers, and underscores only"
required
>
<div class="error">
Username must be 3-20 characters (letters, numbers, underscores)
</div>
</div>
<div class="form-group">
<label for="email">
Email Address <span class="required">*</span>
</label>
<input
id="email"
type="email"
name="email"
placeholder="[email protected]"
required
>
<div class="error">
Please enter a valid email address
</div>
</div>
<div class="form-group">
<label for="password">
Password <span class="required">*</span>
</label>
<input
id="password"
type="password"
name="password"
minlength="8"
placeholder="Min 8 characters"
required
>
<div class="error">
Password must be at least 8 characters
</div>
</div>
<div class="form-group">
<label for="age">
Age <span class="required">*</span>
</label>
<input
id="age"
type="number"
name="age"
min="18"
max="120"
placeholder="18"
required
>
<div class="error">
You must be at least 18 years old
</div>
</div>
<button type="submit">Create Account</button>
</form>
<script>
const form = document.getElementById('signup-form');
form.addEventListener('submit', (event) => {
if (!form.checkValidity()) {
event.preventDefault();
alert('Please fix the errors in the form');
} else {
event.preventDefault();
alert('Form is valid! Ready to submit.');
}
});
</script>
</body>
</html>Validation Best Practices
✅ Validate early and often - Give feedback as users type, not just on submit ✅ Be specific - “Email must contain @” is better than “Invalid format” ✅ Use appropriate input types - Let the browser help with validation ✅ Make required fields obvious - Mark them clearly ✅ Test with keyboard only - Ensure validation works without a mouse ✅ Show success states - Confirm when fields are correct ✅ Always validate server-side too - Never trust client-side validation alone
Common Mistakes to Avoid
❌ Showing errors before users finish typing ❌ Using vague error messages (“Invalid input”) ❌ Validating on every keystroke (too annoying) ❌ Relying only on color for errors (bad for accessibility) ❌ Not explaining what format is expected ❌ Disabling paste in password fields ❌ Trusting client-side validation for security
Keep Learning
- Building Accessible Forms - Complete accessibility guide
- HTML Forms Tutorial - Learn form basics
- Common HTML Mistakes - Avoid form errors
- Explore Templates - See form examples
Try form validation in the htmlEditor.net playground right now!
Remember: Good validation helps users succeed, not just catches errors. Make it clear, helpful, and forgiving.