Form validation ensures users enter the correct data before submitting a form. HTML5 provides built-in validation attributes that work without JavaScript, making forms more user-friendly and reducing errors. This improves data quality and user experience.
Why Form Validation Matters
Proper form validation provides several benefits:
- Prevents incomplete form submissions
- Catches errors before data reaches the server
- Provides immediate feedback to users
- Reduces server load and processing time
- Improves data quality and accuracy
- Enhances user experience with clear guidance
The required Attribute
The required attribute makes a field mandatory. Users cannot submit the form until required fields are filled.
<form>
<!-- Required text input -->
<label for="name">Full Name: *</label>
<input type="text" id="name" name="name" required>
<!-- Required email -->
<label for="email">Email: *</label>
<input type="email" id="email" name="email" required>
<!-- Required select -->
<label for="country">Country: *</label>
<select id="country" name="country" required>
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
</select>
<!-- Required checkbox -->
<input type="checkbox" id="terms" name="terms" required>
<label for="terms">I agree to the terms and conditions *</label>
<button type="submit">Submit</button>
</form>The pattern Attribute
The pattern attribute uses regular expressions to validate input format. It's perfect for enforcing specific formats like phone numbers, postal codes, or custom patterns.
Common Pattern Examples
<!-- US Phone Number: (123) 456-7890 -->
<label for="phone">Phone Number:</label>
<input
type="tel"
id="phone"
name="phone"
pattern="([0-9]{3}) [0-9]{3}-[0-9]{4}"
placeholder="(123) 456-7890"
title="Format: (123) 456-7890"
>
<!-- US Zip Code: 12345 or 12345-6789 -->
<label for="zip">Zip Code:</label>
<input
type="text"
id="zip"
name="zip"
pattern="[0-9]{5}(-[0-9]{4})?"
placeholder="12345 or 12345-6789"
title="5 digits or 5+4 format"
>
<!-- Username: 3-16 alphanumeric characters -->
<label for="username">Username:</label>
<input
type="text"
id="username"
name="username"
pattern="[a-zA-Z0-9]{3,16}"
placeholder="username123"
title="3-16 letters and numbers only"
>
<!-- Alphanumeric only -->
<label for="code">Product Code:</label>
<input
type="text"
id="code"
name="code"
pattern="[A-Z0-9]+"
placeholder="ABC123"
title="Uppercase letters and numbers only"
>
<!-- Credit Card: 16 digits -->
<label for="card">Credit Card Number:</label>
<input
type="text"
id="card"
name="card"
pattern="[0-9]{16}"
placeholder="1234567890123456"
title="16 digits"
maxlength="16"
>💡 Pro Tip:
Always use the title attribute with pattern to explain the expected format. This message appears when validation fails.
Length Validation
Control the length of text input with minlength and maxlength attributes.
<!-- Minimum length -->
<label for="password">Password (minimum 8 characters):</label>
<input
type="password"
id="password"
name="password"
minlength="8"
required
>
<!-- Maximum length -->
<label for="username">Username (max 20 characters):</label>
<input
type="text"
id="username"
name="username"
maxlength="20"
required
>
<!-- Both min and max -->
<label for="bio">Bio (50-200 characters):</label>
<textarea
id="bio"
name="bio"
minlength="50"
maxlength="200"
rows="4"
></textarea>Numeric Validation
Use min, max, and step to validate numeric inputs.
<!-- Age validation -->
<label for="age">Age (must be 18 or older):</label>
<input
type="number"
id="age"
name="age"
min="18"
max="120"
required
>
<!-- Quantity with step -->
<label for="quantity">Quantity (in multiples of 5):</label>
<input
type="number"
id="quantity"
name="quantity"
min="5"
max="100"
step="5"
value="5"
>
<!-- Price with decimals -->
<label for="price">Price ($):</label>
<input
type="number"
id="price"
name="price"
min="0.01"
step="0.01"
placeholder="0.00"
required
>
<!-- Date range -->
<label for="appointment">Appointment Date:</label>
<input
type="date"
id="appointment"
name="appointment"
min="2024-01-01"
max="2024-12-31"
required
>Email and URL Validation
Input types like email and url have built-in format validation.
<!-- Email validation (automatic) -->
<label for="email">Email Address:</label>
<input
type="email"
id="email"
name="email"
placeholder="user@example.com"
required
>
<!-- URL validation (automatic) -->
<label for="website">Website URL:</label>
<input
type="url"
id="website"
name="website"
placeholder="https://example.com"
required
>
<!-- Multiple emails -->
<label for="emails">Email Addresses (comma-separated):</label>
<input
type="email"
id="emails"
name="emails"
multiple
placeholder="email1@example.com, email2@example.com"
>Custom Validation Messages
Use JavaScript to customize validation messages for better user experience:
<form>
<label for="username">Username:</label>
<input
type="text"
id="username"
name="username"
pattern="[a-zA-Z0-9]{3,16}"
required
>
<label for="email">Email:</label>
<input
type="email"
id="email"
name="email"
required
>
<button type="submit">Submit</button>
</form>
<script>
const username = document.getElementById('username');
const email = document.getElementById('email');
username.addEventListener('invalid', function(e) {
if (this.validity.valueMissing) {
this.setCustomValidity('Please enter a username');
} else if (this.validity.patternMismatch) {
this.setCustomValidity('Username must be 3-16 characters (letters and numbers only)');
}
});
username.addEventListener('input', function() {
this.setCustomValidity(''); // Clear custom message on input
});
email.addEventListener('invalid', function(e) {
if (this.validity.valueMissing) {
this.setCustomValidity('Please enter your email address');
} else if (this.validity.typeMismatch) {
this.setCustomValidity('Please enter a valid email address');
}
});
email.addEventListener('input', function() {
this.setCustomValidity('');
});
</script>Complete Validated Form Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registration Form with Validation</title>
<style>
form {
max-width: 500px;
margin: 40px auto;
padding: 30px;
border: 1px solid #ddd;
border-radius: 8px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
input, select, textarea {
width: 100%;
padding: 10px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
input:invalid, select:invalid, textarea:invalid {
border-color: #ff6b6b;
}
input:valid, select:valid, textarea:valid {
border-color: #51cf66;
}
small {
display: block;
margin-top: 5px;
color: #666;
font-size: 12px;
}
.required::after {
content: " *";
color: red;
}
button {
width: 100%;
padding: 12px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<form action="/register" method="post">
<h2>User Registration</h2>
<div class="form-group">
<label for="fullname" class="required">Full Name</label>
<input
type="text"
id="fullname"
name="fullname"
minlength="3"
maxlength="50"
required
>
<small>3-50 characters</small>
</div>
<div class="form-group">
<label for="username" class="required">Username</label>
<input
type="text"
id="username"
name="username"
pattern="[a-zA-Z0-9_]{3,20}"
title="3-20 characters: letters, numbers, and underscore only"
required
>
<small>3-20 characters (letters, numbers, underscore)</small>
</div>
<div class="form-group">
<label for="email" class="required">Email</label>
<input
type="email"
id="email"
name="email"
required
>
</div>
<div class="form-group">
<label for="phone">Phone Number</label>
<input
type="tel"
id="phone"
name="phone"
pattern="([0-9]{3}) [0-9]{3}-[0-9]{4}"
placeholder="(123) 456-7890"
title="Format: (123) 456-7890"
>
<small>Optional - Format: (123) 456-7890</small>
</div>
<div class="form-group">
<label for="password" class="required">Password</label>
<input
type="password"
id="password"
name="password"
minlength="8"
pattern="(?=.*d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
title="Must contain at least one number, one uppercase and lowercase letter, and at least 8 characters"
required
>
<small>Min 8 characters with uppercase, lowercase, and number</small>
</div>
<div class="form-group">
<label for="age" class="required">Age</label>
<input
type="number"
id="age"
name="age"
min="18"
max="120"
required
>
<small>Must be 18 or older</small>
</div>
<div class="form-group">
<label for="country" class="required">Country</label>
<select id="country" name="country" required>
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
<option value="au">Australia</option>
</select>
</div>
<div class="form-group">
<label for="bio">Bio</label>
<textarea
id="bio"
name="bio"
minlength="20"
maxlength="200"
rows="4"
></textarea>
<small>Optional - 20-200 characters if provided</small>
</div>
<div class="form-group">
<input type="checkbox" id="terms" name="terms" required>
<label for="terms" class="required">
I agree to the Terms and Conditions
</label>
</div>
<button type="submit">Register</button>
</form>
</body>
</html>CSS Validation States
Style inputs based on their validation state using CSS pseudo-classes:
/* Valid input */
input:valid {
border-color: #51cf66;
}
/* Invalid input */
input:invalid {
border-color: #ff6b6b;
}
/* Required but empty */
input:required {
border-left: 4px solid #ffd43b;
}
/* Optional field */
input:optional {
border-left: 4px solid #adb5bd;
}
/* In range (for number/date inputs) */
input:in-range {
border-color: #51cf66;
}
/* Out of range */
input:out-of-range {
border-color: #ff6b6b;
}
/* Only show validation after user interaction */
input:not(:placeholder-shown):invalid {
border-color: #ff6b6b;
background-color: #fff5f5;
}
input:not(:placeholder-shown):valid {
border-color: #51cf66;
background-color: #f0fff4;
}Disabling Validation
Use novalidate on the form or formnovalidate on buttons to skip validation:
<!-- Disable validation for entire form -->
<form action="/submit" method="post" novalidate>
<input type="email" required>
<button type="submit">Submit</button>
</form>
<!-- Multiple buttons with different validation -->
<form action="/submit" method="post">
<input type="email" name="email" required>
<!-- Submit with validation -->
<button type="submit">Submit</button>
<!-- Save draft without validation -->
<button type="submit" formnovalidate formaction="/save-draft">
Save as Draft
</button>
</form>Best Practices
Provide Clear Feedback
Make validation messages helpful and specific:
<!-- Good: Specific guidance -->
<label for="password">Password:</label>
<input
type="password"
id="password"
pattern="(?=.*d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
title="Password must be at least 8 characters and include uppercase, lowercase, and numbers"
>
<small>Must be 8+ characters with uppercase, lowercase, and number</small>
<!-- Bad: Vague -->
<input type="password" title="Invalid password">Validate on Both Client and Server
Client-side validation improves user experience but should NEVER be your only validation. Always validate data on the server side for security. Users can bypass client-side validation, so server validation is essential.
Clearly Indicate Required Fields
Mark required fields consistently:
<style>
.required::after {
content: " *";
color: red;
}
</style>
<form>
<label for="name" class="required">Name</label>
<input type="text" id="name" required>
<label for="email" class="required">Email</label>
<input type="email" id="email" required>
<label for="phone">Phone (optional)</label>
<input type="tel" id="phone">
<p><small>* Required fields</small></p>
</form>Accessibility Considerations
- Always provide clear labels for all inputs
- Use the
titleattribute withpatternfor guidance - Include helpful hints with
<small>oraria-describedby - Ensure error messages are announced to screen readers
- Don't rely solely on color to indicate validation state
- Make sure validation messages are keyboard accessible
- Test with screen readers to ensure validation is announced
Key Takeaways
- Use
requiredto make fields mandatory - The
patternattribute validates format with regex minlengthandmaxlengthcontrol text lengthmin,max, andstepvalidate numbers and dates- Always include the
titleattribute withpattern - Use CSS pseudo-classes to style validation states
- Provide clear, helpful validation messages
- Always validate on both client and server sides
What's Next?
Now that you understand form validation, the next lesson will cover form buttons—including submit, reset, and regular button elements, and how to use them effectively in your forms.