Introduction to Form Validation

Form validation ensures that users enter data in the correct format and that required fields are filled before submission. HTML5 provides built-in validation attributes, while JavaScript allows for custom validation logic.

Built-in HTML5 Validation

HTML5 offers several validation attributes that work without any JavaScript:

Required Fields



html

<!-- Basic required validation -->
<label for="email">Email (required):</label>
<input type="email" id="email" name="email" required>

<label for="password">Password (required):</label>
<input type="password" id="password" name="password" required>

<label for="terms">
  <input type="checkbox" id="terms" name="terms" required>
  I agree to the terms and conditions
</label>

Length Validation



html

<!-- Minimum and maximum length -->
<label for="username">Username (3-20 characters):</label>
<input type="text" 
       id="username" 
       name="username" 
       minlength="3" 
       maxlength="20" 
       required>

<!-- Textarea with character limit -->
<label for="bio">Bio (max 500 characters):</label>
<textarea id="bio" 
          name="bio" 
          maxlength="500" 
          placeholder="Tell us about yourself..."></textarea>

Numeric Validation



html

<!-- Number range validation -->
<label for="age">Age (18-100):</label>
<input type="number" 
       id="age" 
       name="age" 
       min="18" 
       max="100" 
       required>

<!-- Step validation -->
<label for="price">Price (increments of 0.50):</label>
<input type="number" 
       id="price" 
       name="price" 
       min="0" 
       step="0.50" 
       placeholder="0.00">

<!-- Range slider -->
<label for="rating">Rating (1-10):</label>
<input type="range" 
       id="rating" 
       name="rating" 
       min="1" 
       max="10" 
       value="5">
<output for="rating">5</output>

Pattern Validation



html

<!-- Phone number pattern -->
<label for="phone">Phone (XXX-XXX-XXXX):</label>
<input type="tel" 
       id="phone" 
       name="phone" 
       pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" 
       placeholder="123-456-7890" 
       title="Please enter phone in format: XXX-XXX-XXXX">

<!-- Username pattern -->
<label for="username">Username (letters, numbers, underscore only):</label>
<input type="text" 
       id="username" 
       name="username" 
       pattern="[a-zA-Z0-9_]+" 
       title="Username can only contain letters, numbers, and underscores" 
       required>

<!-- Password complexity -->
<label for="password">Password (min 8 chars, 1 uppercase, 1 number):</label>
<input type="password" 
       id="password" 
       name="password" 
       pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}" 
       title="Password must be at least 8 characters with 1 uppercase letter and 1 number" 
       required>

Input Type Validation



html

<!-- Automatic email validation -->
<label for="email">Email:</label>
<input type="email" 
       id="email" 
       name="email" 
       placeholder="user@example.com" 
       required>

<!-- URL validation -->
<label for="website">Website:</label>
<input type="url" 
       id="website" 
       name="website" 
       placeholder="https://example.com">

<!-- Date validation -->
<label for="start-date">Start Date:</label>
<input type="date" 
       id="start-date" 
       name="start-date" 
       min="2024-01-01" 
       max="2025-12-31" 
       required>

Custom Validation with JavaScript

For more complex validation logic, JavaScript provides powerful tools:

Basic Custom Validation



html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Custom Validation Example</title>
  <style>
    .error {
      color: red;
      font-size: 0.9em;
      margin-top: 5px;
    }
    
    .invalid {
      border-color: red;
    }
    
    .valid {
      border-color: green;
    }
    
    .form-group {
      margin-bottom: 15px;
    }
    
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    
    input {
      width: 100%;
      padding: 8px;
      border: 1px solid #ddd;
      border-radius: 4px;
      box-sizing: border-box;
    }
  </style>
</head>
<body>
  <form id="registration-form" novalidate>
    <div class="form-group">
      <label for="email">Email:</label>
      <input type="email" id="email" name="email" required>
      <div class="error" id="email-error"></div>
    </div>
    
    <div class="form-group">
      <label for="password">Password:</label>
      <input type="password" id="password" name="password" required>
      <div class="error" id="password-error"></div>
    </div>
    
    <div class="form-group">
      <label for="confirm-password">Confirm Password:</label>
      <input type="password" id="confirm-password" name="confirm-password" required>
      <div class="error" id="confirm-password-error"></div>
    </div>
    
    <button type="submit">Register</button>
  </form>
  
  <script>
    const form = document.getElementById('registration-form');
    const emailInput = document.getElementById('email');
    const passwordInput = document.getElementById('password');
    const confirmPasswordInput = document.getElementById('confirm-password');
    
    // Custom validation functions
    function validateEmail(email) {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      return emailRegex.test(email);
    }
    
    function validatePassword(password) {
      // At least 8 characters, 1 uppercase, 1 lowercase, 1 number
      const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/;
      return passwordRegex.test(password);
    }
    
    function showError(inputElement, message) {
      const errorElement = document.getElementById(inputElement.id + '-error');
      errorElement.textContent = message;
      inputElement.classList.add('invalid');
      inputElement.classList.remove('valid');
    }
    
    function showSuccess(inputElement) {
      const errorElement = document.getElementById(inputElement.id + '-error');
      errorElement.textContent = '';
      inputElement.classList.add('valid');
      inputElement.classList.remove('invalid');
    }
    
    // Real-time validation
    emailInput.addEventListener('blur', function() {
      if (this.value === '') {
        showError(this, 'Email is required');
      } else if (!validateEmail(this.value)) {
        showError(this, 'Please enter a valid email address');
      } else {
        showSuccess(this);
      }
    });
    
    passwordInput.addEventListener('blur', function() {
      if (this.value === '') {
        showError(this, 'Password is required');
      } else if (!validatePassword(this.value)) {
        showError(this, 'Password must be at least 8 characters with 1 uppercase, 1 lowercase, and 1 number');
      } else {
        showSuccess(this);
      }
    });
    
    confirmPasswordInput.addEventListener('blur', function() {
      if (this.value === '') {
        showError(this, 'Please confirm your password');
      } else if (this.value !== passwordInput.value) {
        showError(this, 'Passwords do not match');
      } else {
        showSuccess(this);
      }
    });
    
    // Form submission validation
    form.addEventListener('submit', function(e) {
      e.preventDefault();
      
      let isValid = true;
      
      // Validate all fields
      if (!validateEmail(emailInput.value)) {
        showError(emailInput, 'Please enter a valid email address');
        isValid = false;
      }
      
      if (!validatePassword(passwordInput.value)) {
        showError(passwordInput, 'Password must be at least 8 characters with 1 uppercase, 1 lowercase, and 1 number');
        isValid = false;
      }
      
      if (confirmPasswordInput.value !== passwordInput.value) {
        showError(confirmPasswordInput, 'Passwords do not match');
        isValid = false;
      }
      
      if (isValid) {
        alert('Form submitted successfully!');
        // Here you would normally submit the form to the server
      }
    });
  </script>
</body>
</html>

Advanced Validation Techniques

Using the Constraint Validation API



html

<script>
  // Using HTML5 Constraint Validation API
  function customValidation() {
    const input = document.getElementById('custom-input');
    
    // Check if input is valid
    if (input.validity.valid) {
      // Input is valid
      input.setCustomValidity('');
    } else {
      // Set custom error message
      if (input.validity.valueMissing) {
        input.setCustomValidity('This field is required');
      } else if (input.validity.tooShort) {
        input.setCustomValidity('Input is too short');
      } else if (input.validity.patternMismatch) {
        input.setCustomValidity('Input format is incorrect');
      }
    }
    
    // Report validity
    input.reportValidity();
  }
</script>
Async Validation (Username Check)



html

<div class="form-group">
  <label for="username">Username:</label>
  <input type="text" id="username" name="username" required>
  <div class="error" id="username-error"></div>
  <div class="loading" id="username-loading" style="display: none;">Checking availability...</div>
</div>

<script>
  let usernameTimeout;
  
  document.getElementById('username').addEventListener('input', function() {
    clearTimeout(usernameTimeout);
    const username = this.value;
    
    if (username.length >= 3) {
      // Debounce the API call
      usernameTimeout = setTimeout(() => {
        checkUsernameAvailability(username);
      }, 500);
    }
  });
  
  async function checkUsernameAvailability(username) {
    const loadingElement = document.getElementById('username-loading');
    const errorElement = document.getElementById('username-error');
    const inputElement = document.getElementById('username');
    
    loadingElement.style.display = 'block';
    
    try {
      // Simulate API call
      const response = await fetch(`/api/check-username?username=${username}`);
      const data = await response.json();
      
      if (data.available) {
        showSuccess(inputElement);
      } else {
        showError(inputElement, 'Username is already taken');
      }
    } catch (error) {
      showError(inputElement, 'Error checking username availability');
    } finally {
      loadingElement.style.display = 'none';
    }
  }
</script>

Complete Validation Example



html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Complete Form Validation</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 500px;
      margin: 20px auto;
      padding: 20px;
    }
    
    .form-group {
      margin-bottom: 20px;
    }
    
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    
    input, textarea {
      width: 100%;
      padding: 10px;
      border: 2px solid #ddd;
      border-radius: 4px;
      box-sizing: border-box;
      transition: border-color 0.3s;
    }
    
    input:focus, textarea:focus {
      outline: none;
      border-color: #007bff;
    }
    
    .invalid {
      border-color: #dc3545;
    }
    
    .valid {
      border-color: #28a745;
    }
    
    .error {
      color: #dc3545;
      font-size: 0.9em;
      margin-top: 5px;
      display: block;
    }
    
    .success {
      color: #28a745;
      font-size: 0.9em;
      margin-top: 5px;
      display: block;
    }
    
    button {
      background-color: #007bff;
      color: white;
      padding: 12px 24px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
    }
    
    button:hover {
      background-color: #0056b3;
    }
    
    button:disabled {
      background-color: #6c757d;
      cursor: not-allowed;
    }
    
    .strength-meter {
      height: 5px;
      background-color: #f0f0f0;
      border-radius: 3px;
      margin-top: 5px;
      overflow: hidden;
    }
    
    .strength-bar {
      height: 100%;
      transition: width 0.3s, background-color 0.3s;
    }
    
    .strength-weak { background-color: #dc3545; }
    .strength-fair { background-color: #ffc107; }
    .strength-good { background-color: #28a745; }
    .strength-strong { background-color: #007bff; }
  </style>
</head>
<body>
  <form id="complete-form" novalidate>
    <h2>Registration Form</h2>
    
    <div class="form-group">
      <label for="full-name">Full Name:</label>
      <input type="text" id="full-name" name="full-name" required>
      <span class="error" id="full-name-error"></span>
    </div>
    
    <div class="form-group">
      <label for="email">Email:</label>
      <input type="email" id="email" name="email" required>
      <span class="error" id="email-error"></span>
    </div>
    
    <div class="form-group">
      <label for="password">Password:</label>
      <input type="password" id="password" name="password" required>
      <div class="strength-meter">
        <div class="strength-bar" id="strength-bar"></div>
      </div>
      <span class="error" id="password-error"></span>
      <span id="password-strength"></span>
    </div>
    
    <div class="form-group">
      <label for="confirm-password">Confirm Password:</label>
      <input type="password" id="confirm-password" name="confirm-password" required>
      <span class="error" id="confirm-password-error"></span>
    </div>
    
    <div class="form-group">
      <label for="age">Age:</label>
      <input type="number" id="age" name="age" min="13" max="120" required>
      <span class="error" id="age-error"></span>
    </div>
    
    <button type="submit" id="submit-btn">Register</button>
  </form>
  
  <script>
    const form = document.getElementById('complete-form');
    const inputs = form.querySelectorAll('input[required]');
    
    // Validation rules
    const validators = {
      'full-name': {
        validate: (value) => value.trim().length >= 2,
        message: 'Full name must be at least 2 characters long'
      },
      'email': {
        validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
        message: 'Please enter a valid email address'
      },
      'password': {
        validate: (value) => value.length >= 8 && /(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value),
        message: 'Password must be at least 8 characters with uppercase, lowercase, and number'
      },
      'confirm-password': {
        validate: (value) => value === document.getElementById('password').value,
        message: 'Passwords do not match'
      },
      'age': {
        validate: (value) => value >= 13 && value <= 120,
        message: 'Age must be between 13 and 120'
      }
    };
    
    // Password strength checker
    function checkPasswordStrength(password) {
      let strength = 0;
      const strengthBar = document.getElementById('strength-bar');
      const strengthText = document.getElementById('password-strength');
      
      if (password.length >= 8) strength++;
      if (/[a-z]/.test(password)) strength++;
      if (/[A-Z]/.test(password)) strength++;
      if (/\d/.test(password)) strength++;
      if (/[^a-zA-Z\d]/.test(password)) strength++;
      
      const strengthLevels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong'];
      const strengthClasses = ['strength-weak', 'strength-weak', 'strength-fair', 'strength-good', 'strength-strong'];
      
      strengthBar.style.width = `${(strength / 5) * 100}%`;
      strengthBar.className = `strength-bar ${strengthClasses[strength]}`;
      strengthText.textContent = strengthLevels[strength] || '';
      strengthText.className = strength >= 3 ? 'success' : 'error';
    }
    
    // Validation functions
    function showError(input, message) {
      const errorElement = document.getElementById(`${input.id}-error`);
      errorElement.textContent = message;
      input.classList.add('invalid');
      input.classList.remove('valid');
    }
    
    function showSuccess(input) {
      const errorElement = document.getElementById(`${input.id}-error`);
      errorElement.textContent = '';
      input.classList.add('valid');
      input.classList.remove('invalid');
    }
    
    function validateInput(input) {
      const validator = validators[input.id];
      if (!validator) return true;
      
      if (input.value.trim() === '') {
        showError(input, 'This field is required');
        return false;
      }
      
      if (!validator.validate(input.value)) {
        showError(input, validator.message);
        return false;
      }
      
      showSuccess(input);
      return true;
    }
    
    // Real-time validation
    inputs.forEach(input => {
      input.addEventListener('blur', () => validateInput(input));
      input.addEventListener('input', () => {
        if (input.id === 'password') {
          checkPasswordStrength(input.value);
        }
        if (input.classList.contains('invalid')) {
          validateInput(input);
        }
      });
    });
    
    // Form submission
    form.addEventListener('submit', function(e) {
      e.preventDefault();
      
      let isValid = true;
      inputs.forEach(input => {
        if (!validateInput(input)) {
          isValid = false;
        }
      });
      
      if (isValid) {
        alert('Registration successful!');
        form.reset();
        // Clear validation classes
        inputs.forEach(input => {
          input.classList.remove('valid', 'invalid');
          document.getElementById(`${input.id}-error`).textContent = '';
        });
        document.getElementById('strength-bar').style.width = '0';
        document.getElementById('password-strength').textContent = '';
      }
    });
  </script>
</body>
</html>

Best Practices for Form Validation

  1. Validate on both client and server - Client-side validation improves UX, server-side prevents malicious data
  2. Provide real-time feedback - Validate on blur or input events for immediate feedback
  3. Use clear error messages - Be specific about what's wrong and how to fix it
  4. Progressive enhancement - Forms should work without JavaScript
  5. Accessibility - Use ARIA attributes for screen readers
  6. Debounce API calls - Prevent excessive server requests during typing