· 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-required in addition to required
  • 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

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.

← Back to all blog posts

    Share: