Creating a Dynamic Form Validator
In this tutorial, we'll build a client-side form validator using only vanilla JavaScript. No jQuery, no validation libraries - just pure JavaScript that you control and understand.
Why Build a Custom Form Validator?
Form validation is a fundamental part of web development. While browsers provide some basic validation through HTML5 attributes like required
, min
, max
, etc., custom JavaScript validation gives you:
- More precise control over validation rules
- Better user experience with immediate feedback
- Consistent validation across all browsers
- The ability to validate complex related fields
The HTML Structure
<form id="signup-form" class="form" novalidate>
<div class="form-group"> <label for="username">Username</label> <input type="text" id="username" name="username" data-validate data-min-length="3" data-max-length="15"> <div class="error-message"></div> </div>
<div class="form-group"> <label for="email">Email</label> <input type="email" id="email" name="email" data-validate> <div class="error-message"></div> </div>
<div class="form-group"> <label for="password">Password</label> <input type="password" id="password" name="password" data-validate data-min-length="8"> <div class="error-message"></div> </div>
<div class="form-group"> <label for="confirm-password">Confirm Password</label> <input type="password" id="confirm-password" name="confirmPassword" data-validate data-matches="password"> <div class="error-message"></div> </div>
<button type="submit">Sign Up</button> </form>
Notice the following attributes:
novalidate
- Disables the browser's built-in validation, allowing our JavaScript to take overdata-validate
- Marks a field for validationdata-min-length
,data-max-length
- Sets length constraintsdata-matches
- Specifies another field to match (for password confirmation)
The CSS Styling
/ Form validation styles /
.form-group { margin-bottom: 1.5rem; position: relative; }
.form-group label { display: block; margin-bottom: 0.5rem; font-weight: 600; }
.form-group input { width: 100%; padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; transition: border-color 0.3s ease; }
.form-group input.error { border-color: #e74c3c; }
.form-group input.success { border-color: #2ecc71; }
.error-message { color: #e74c3c; font-size: 0.85rem; margin-top: 0.5rem; min-height: 1.2rem; }
/ Show a check mark for valid fields / .form-group.success:after { content: '✓'; position: absolute; right: 12px; top: 37px; color: #2ecc71; font-size: 1.2rem; }
/ Show an exclamation mark for invalid fields / .form-group.error:after { content: '!'; position: absolute; right: 12px; top: 37px; color: #e74c3c; font-size: 1.2rem; font-weight: bold; }
The JavaScript Validator
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('signup-form');
if (!form) return;
// Set up the validation rules const validationRules = { username: { required: true, minLength: 3, maxLength: 15 }, email: { required: true, email: true }, password: { required: true, minLength: 8 }, confirmPassword: { required: true, matches: 'password' } };
// Get all inputs that need validation const inputs = form.querySelectorAll('[data-validate]');
// Add event listeners to all inputs for real-time validation inputs.forEach(input => { input.addEventListener('blur', () => { validateField(input); });
input.addEventListener('input', () => { // Clear error as user is typing const formGroup = input.parentElement; formGroup.classList.remove('error'); formGroup.classList.remove('success'); formGroup.querySelector('.error-message').textContent = ''; }); });
// Add submit event listener to the form form.addEventListener('submit', function(e) { let isValid = true;
// Validate all fields inputs.forEach(input => { if (!validateField(input)) { isValid = false; } });
// Prevent form submission if validation fails if (!isValid) { e.preventDefault(); } else { alert('Form submitted successfully!'); form.reset(); // In a real app, you would submit the form or use fetch/AJAX here } });
// Field validation function function validateField(input) { const fieldName = input.name; const value = input.value.trim(); const formGroup = input.parentElement; const errorElement = formGroup.querySelector('.error-message');
// Reset validation state formGroup.classList.remove('error'); formGroup.classList.remove('success'); errorElement.textContent = '';
let isValid = true;
// Check required if (validationRules[fieldName]?.required && value === '') { showError(formGroup, errorElement, 'This field is required'); isValid = false; }
// Check min length else if (validationRules[fieldName]?.minLength && value.length < input.dataset.minLength) { showError(formGroup, errorElement, Must be at least ${input.dataset.minLength} characters
); isValid = false; }
// Check max length else if (validationRules[fieldName]?.maxLength && value.length > input.dataset.maxLength) { showError(formGroup, errorElement, Must be less than ${input.dataset.maxLength} characters
); isValid = false; }
// Check email format else if (validationRules[fieldName]?.email && !isValidEmail(value)) { showError(formGroup, errorElement, 'Please enter a valid email address'); isValid = false; }
// Check if field matches another field (for password confirmation) else if (validationRules[fieldName]?.matches) { const matchField = form.querySelector([name="${input.dataset.matches}"]
); if (matchField && value !== matchField.value) { showError(formGroup, errorElement, 'Fields do not match'); isValid = false; } }
// Set success state if field is valid if (isValid && value !== '') { formGroup.classList.add('success'); }
return isValid; }
// Helper function to show error messages function showError(formGroup, errorElement, message) { formGroup.classList.add('error'); errorElement.textContent = message; }
// Helper function to validate email format function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } });
How It Works
Our form validator uses a data-attribute approach to keep the HTML clean and the JavaScript flexible:
- Set up validation rules: We define validation rules for each field in our JavaScript
- Event listeners: We add event listeners for 'blur' (when a field loses focus) and 'input' (as the user types)
- Custom validation: Each field is validated against its specific rules
- Visual feedback: We add success/error classes to provide visual cues to the user
- Form submission: The form only submits if all fields pass validation
Extending The Form Validator
This validator is designed to be extensible. Here are some ways you can enhance it:
- Custom validation rules: Add more complex validation rules like password strength checks
- Error summaries: Create a summary of all errors at the top of the form
- Asynchronous validation: Check if a username or email already exists by making an API call
- Localization: Support multiple languages for error messages
Benefits of Vanilla JavaScript Validation
By using vanilla JavaScript for form validation, you:
- Reduce dependencies and page load time
- Have full control over validation logic and error messages
- Understand exactly how your validation works
- Can easily adapt the validator for different forms and requirements
Conclusion
You've now built a flexible, reusable form validation system using only vanilla JavaScript. By understanding how form validation works at its core, you've gained knowledge that will help you regardless of what libraries or frameworks you might use in the future.
Remember, while client-side validation improves user experience, you should always validate data on the server as well for security. Client-side validation can be bypassed, but it helps users correct mistakes before submitting the form.