MSDN Documentation: Form Validation Examples

Mastering Form Validation with HTML5 and JavaScript

This page demonstrates various techniques for validating user input in web forms, leveraging both native HTML5 attributes and custom JavaScript solutions. Effective form validation is crucial for data integrity, user experience, and security.

Why Validate?

Example 1: HTML5 Native Validation

HTML5 provides a powerful set of built-in validation attributes that require no JavaScript for basic checks. Browsers automatically handle the validation and display error messages.

Registration Form

Username must be between 4 and 20 characters and can only contain letters, numbers, and underscores.
Please enter a valid email address.
Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, and one number.
Passwords do not match.

Contact Form

Your name is required.
Subject is required.
Your message cannot be empty.
Explanation
HTML
CSS
JavaScript (for custom messages)

How it Works

We use attributes like required, minlength, maxlength, and pattern directly on the form elements.

The browser's default validation UI will appear when the user tries to submit the form with invalid data. For more control over the appearance and content of error messages, JavaScript is needed.

HTML Code

<form id="html5-form">
    <div>
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required minlength="4" maxlength="20" pattern="[A-Za-z0-9_]+">
        <div class="error-message" data-for="username">Username must be between 4 and 20 characters and can only contain letters, numbers, and underscores.</div>
    </div>
    <div>
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required>
        <div class="error-message" data-for="email">Please enter a valid email address.</div>
    </div>
    <div>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required minlength="8" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}">
        <div class="error-message" data-for="password">Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, and one number.</div>
    </div>
    <div>
        <label for="confirm-password">Confirm Password:</label>
        <input type="password" id="confirm-password" name="confirm-password" required>
        <div class="error-message" data-for="confirm-password">Passwords do not match.</div>
    </div>
    <button type="submit">Register</button>
</form>

<form id="contact-form-html5">
    <div>
        <label for="name">Name:</label>
        <input type="text" id="name" name="name" required>
        <div class="error-message" data-for="name">Your name is required.</div>
    </div>
    <div>
        <label for="subject">Subject:</label>
        <input type="text" id="subject" name="subject" required>
        <div class="error-message" data-for="subject">Subject is required.</div>
    </div>
    <div>
        <label for="message">Message:</label>
        <textarea id="message" name="message" rows="4" required></textarea>
        <div class="error-message" data-for="message">Your message cannot be empty.</div>
    </div>
    <button type="submit">Send Message</button>
</form>

CSS Styling (for Native Validation)

You can use pseudo-classes like :valid and :invalid to style form elements based on their validation state. However, custom error message display requires JavaScript.

/* Styles for input elements */
input:not([type="submit"]):not([type="button"]), textarea {
    border: 1px solid #ccc;
}

/* Styles for invalid input elements */
input:not([type="submit"]):not([type="button"]):invalid,
textarea:invalid {
    border-color: #e51400; /* Red border for invalid input */
    box-shadow: 0 0 5px rgba(229, 20, 0, 0.3);
}

/* Hide the custom error messages by default */
.error-message {
    color: #e51400;
    font-size: 0.9em;
    margin-top: -10px;
    margin-bottom: 15px;
    display: none;
}

/* Show the custom error message when the associated input is invalid and has been interacted with (or submitted) */
input:not([type="submit"]):not([type="button"]):invalid ~ .error-message,
textarea:invalid ~ .error-message {
    display: block;
}

/* Override for password confirmation if it's invalid */
#confirm-password:invalid ~ .error-message {
    display: block;
}

/* Styles for the form container and cards are in the main stylesheet */
.form-container {
    display: flex;
    gap: 30px;
    flex-wrap: wrap;
}

.form-card {
    flex: 1;
    min-width: 300px;
    background-color: #ffffff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}

.form-card label {
    display: block;
    margin-bottom: 8px;
    font-weight: bold;
    color: #005a9e;
}

.form-card input[type="text"],
.form-card input[type="email"],
.form-card input[type="password"],
.form-card textarea {
    width: calc(100% - 16px);
    padding: 10px;
    margin-bottom: 15px;
    border: 1px solid #ccc;
    border-radius: 4px;
    box-sizing: border-box;
    font-size: 1em;
}

.form-card input:focus,
.form-card textarea:focus {
    outline: none;
    border-color: #0078d4;
    box-shadow: 0 0 5px rgba(0, 120, 212, 0.3);
}

.form-card button {
    background-color: #0078d4;
    color: white;
    padding: 12px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1.1em;
    transition: background-color 0.3s ease;
}

.form-card button:hover {
    background-color: #005a9e;
}

/* Custom error message styling */
.error-message {
    color: #e51400;
    font-size: 0.9em;
    margin-top: -10px;
    margin-bottom: 15px;
    display: none; /* Hidden by default */
}

.error-message.visible {
    display: block;
}

JavaScript for Custom Error Messages

While HTML5 validation is great, you often need to display your own error messages or perform more complex checks. Here's how you can supplement native validation.

document.addEventListener('DOMContentLoaded', function() {
    // --- HTML5 Form Validation ---
    const html5Form = document.getElementById('html5-form');
    const usernameInput = document.getElementById('username');
    const emailInput = document.getElementById('email');
    const passwordInput = document.getElementById('password');
    const confirmPasswordInput = document.getElementById('confirm-password');

    const usernameError = document.querySelector('.error-message[data-for="username"]');
    const emailError = document.querySelector('.error-message[data-for="email"]');
    const passwordError = document.querySelector('.error-message[data-for="password"]');
    const confirmPasswordError = document.querySelector('.error-message[data-for="confirm-password"]');

    // Function to show custom error message
    function showCustomError(inputElement, errorMessageElement, message) {
        inputElement.classList.add('invalid');
        errorMessageElement.textContent = message;
        errorMessageElement.classList.add('visible');
    }

    // Function to hide custom error message
    function hideCustomError(inputElement, errorMessageElement) {
        inputElement.classList.remove('invalid');
        errorMessageElement.classList.remove('visible');
    }

    // Event listener for form submission
    html5Form.addEventListener('submit', function(event) {
        // Prevent default submission
        event.preventDefault();

        let isValid = true;

        // Username validation
        if (!usernameInput.validity.valid) {
            if (usernameInput.value.length < 4) {
                showCustomError(usernameInput, usernameError, "Username must be at least 4 characters.");
            } else if (usernameInput.value.length > 20) {
                showCustomError(usernameInput, usernameError, "Username cannot exceed 20 characters.");
            } else if (!/^[A-Za-z0-9_]+$/.test(usernameInput.value)) {
                showCustomError(usernameInput, usernameError, "Username can only contain letters, numbers, and underscores.");
            } else {
                hideCustomError(usernameInput, usernameError);
            }
            isValid = false;
        } else {
            hideCustomError(usernameInput, usernameError);
        }

        // Email validation
        if (!emailInput.validity.valid) {
            showCustomError(emailInput, emailError, "Please enter a valid email address.");
            isValid = false;
        } else {
            hideCustomError(emailInput, emailError);
        }

        // Password validation
        const passwordRegex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;
        if (!passwordInput.validity.valid) {
            if (passwordInput.value.length < 8) {
                showCustomError(passwordInput, passwordError, "Password must be at least 8 characters.");
            } else if (!/[a-z]/.test(passwordInput.value)) {
                showCustomError(passwordInput, passwordError, "Password must include a lowercase letter.");
            } else if (!/[A-Z]/.test(passwordInput.value)) {
                showCustomError(passwordInput, passwordError, "Password must include an uppercase letter.");
            } else if (!/\d/.test(passwordInput.value)) {
                showCustomError(passwordInput, passwordError, "Password must include a number.");
            } else {
                hideCustomError(passwordInput, passwordError);
            }
            isValid = false;
        } else {
            hideCustomError(passwordInput, passwordError);
        }

        // Confirm password validation
        if (confirmPasswordInput.value !== passwordInput.value) {
            showCustomError(confirmPasswordInput, confirmPasswordError, "Passwords do not match.");
            isValid = false;
        } else if (!confirmPasswordInput.validity.valid) {
             // If it's valid but doesn't match, the above check handles it.
             // This handles cases where confirm-password itself might have other native constraints if added.
            hideCustomError(confirmPasswordInput, confirmPasswordError);
        } else {
             hideCustomError(confirmPasswordInput, confirmPasswordError);
        }


        // If all validations pass, simulate form submission (e.g., show success message)
        if (isValid) {
            alert('Form submitted successfully!');
            html5Form.reset(); // Reset the form after successful submission
            // In a real application, you would submit the form data here, e.g., using fetch()
        }
    });

    // --- Contact Form Validation ---
    const contactFormHtml5 = document.getElementById('contact-form-html5');
    const nameInputContact = document.getElementById('name');
    const subjectInputContact = document.getElementById('subject');
    const messageTextareaContact = document.getElementById('message');

    const nameErrorContact = document.querySelector('.error-message[data-for="name"]');
    const subjectErrorContact = document.querySelector('.error-message[data-for="subject"]');
    const messageErrorContact = document.querySelector('.error-message[data-for="message"]');

    contactFormHtml5.addEventListener('submit', function(event) {
        event.preventDefault();
        let isValid = true;

        if (!nameInputContact.validity.valid) {
            showCustomError(nameInputContact, nameErrorContact, "Your name is required.");
            isValid = false;
        } else {
            hideCustomError(nameInputContact, nameErrorContact);
        }

        if (!subjectInputContact.validity.valid) {
            showCustomError(subjectInputContact, subjectErrorContact, "Subject is required.");
            isValid = false;
        } else {
            hideCustomError(subjectInputContact, subjectErrorContact);
        }

        if (!messageTextareaContact.validity.valid) {
            showCustomError(messageTextareaContact, messageErrorContact, "Your message cannot be empty.");
            isValid = false;
        } else {
            hideCustomError(messageTextareaContact, messageErrorContact);
        }

        if (isValid) {
            alert('Contact form submitted successfully!');
            contactFormHtml5.reset();
        }
    });


    // --- Enhancing Native Validation Feedback ---
    // To make the custom error messages appear on invalid input,
    // we can listen to input events and check validity.
    // This is a simplified approach, actual browser behavior can be more nuanced.

    const allInputs = document.querySelectorAll('#html5-form input, #contact-form-html5 input, #contact-form-html5 textarea');

    allInputs.forEach(input => {
        // For password confirmation, we need to check against the password field
        if (input.id === 'confirm-password') {
            input.addEventListener('input', function() {
                const password = document.getElementById('password');
                const errorMsg = document.querySelector(`.error-message[data-for="${input.id}"]`);
                if (input.value !== password.value) {
                    showCustomError(input, errorMsg, "Passwords do not match.");
                } else {
                    hideCustomError(input, errorMsg);
                }
            });
        } else {
            input.addEventListener('input', function() {
                const errorMsg = document.querySelector(`.error-message[data-for="${input.id}"]`);
                if (input.validity.valid) {
                    hideCustomError(input, errorMsg);
                } else {
                    // We are not re-validating the specific pattern here,
                    // relying on the submit handler for detailed custom messages.
                    // This just ensures the error message is shown if the input is generally invalid.
                    // For more granular control, you'd replicate the validation logic here.
                }
            });
        }
    });
});

Example 2: Pure JavaScript Validation

For complex validation rules or when you need full control over the user experience without relying on native HTML5 attributes (though it's usually best to combine them), you can implement validation entirely with JavaScript.

This approach typically involves listening to the submit event, performing checks, and then either submitting the form or displaying error messages.

Advanced Signup Form

Explanation
HTML
CSS
JavaScript

How it Works

This example completely bypasses native HTML5 validation attributes for the fields. Instead, JavaScript listens for the submit event on the form.

Inside the event handler, we perform custom checks for each field. If any check fails, we prevent the form from submitting and display a specific error message next to the field.

HTML Code

<form id="js-validation-form">
    <div>
        <label for="js-username">Username:</label>
        <input type="text" id="js-username" name="js-username">
        <div class="error-message" data-for="js-username"></div>
    </div>
    <div>
        <label for="js-email">Email:</label>
        <input type="text" id="js-email" name="js-email">
        <div class="error-message" data-for="js-email"></div>
    </div>
    <div>
        <label for="js-age">Age:</label>
        <input type="text" id="js-age" name="js-age">
        <div class="error-message" data-for="js-age"></div>
    </div>
    <div>
        <label for="js-country">Country:</label>
        <select id="js-country" name="js-country">
            <option value="">-- Select Country --</option>
            <option value="US">United States</option>
            <option value="CA">Canada</option>
            <option value="UK">United Kingdom</option>
            <option value="DE">Germany</option>
        </select>
        <div class="error-message" data-for="js-country"></div>
    </div>
    <button type="submit">Sign Up</button>
</form>

CSS Code (for JS Validation)

Similar to HTML5 validation, we use CSS to style invalid inputs and display the error messages. The .invalid class is added by JavaScript.

/* Use a dedicated class for invalid states managed by JS */
.form-card input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]),
.form-card select,
.form-card textarea {
    border: 1px solid #ccc;
}

.form-card input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]).invalid,
.form-card select.invalid,
.form-card textarea.invalid {
    border-color: #e51400; /* Red border for invalid input */
    box-shadow: 0 0 5px rgba(229, 20, 0, 0.3);
}

/* Error message styling remains the same */
.error-message {
    color: #e51400;
    font-size: 0.9em;
    margin-top: -10px;
    margin-bottom: 15px;
    display: none; /* Hidden by default */
}

.error-message.visible {
    display: block;
}

/* Styles for the form container and cards are in the main stylesheet */
.form-container {
    display: flex;
    gap: 30px;
    flex-wrap: wrap;
}

.form-card {
    flex: 1;
    min-width: 300px;
    background-color: #ffffff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}

.form-card label {
    display: block;
    margin-bottom: 8px;
    font-weight: bold;
    color: #005a9e;
}

.form-card input[type="text"],
.form-card input[type="email"],
.form-card input[type="password"],
.form-card textarea,
.form-card select { /* Added select styling */
    width: calc(100% - 16px);
    padding: 10px;
    margin-bottom: 15px;
    border: 1px solid #ccc;
    border-radius: 4px;
    box-sizing: border-box;
    font-size: 1em;
}

.form-card input:focus,
.form-card textarea:focus,
.form-card select:focus { /* Added select focus */
    outline: none;
    border-color: #0078d4;
    box-shadow: 0 0 5px rgba(0, 120, 212, 0.3);
}

.form-card button {
    background-color: #0078d4;
    color: white;
    padding: 12px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1.1em;
    transition: background-color 0.3s ease;
}

.form-card button:hover {
    background-color: #005a9e;
}

JavaScript Code

document.addEventListener('DOMContentLoaded', function() {
    const jsForm = document.getElementById('js-validation-form');
    const jsUsernameInput = document.getElementById('js-username');
    const jsEmailInput = document.getElementById('js-email');
    const jsAgeInput = document.getElementById('js-age');
    const jsCountrySelect = document.getElementById('js-country');

    // Helper function to show error messages
    function showError(inputElement, errorMessageElement, message) {
        inputElement.classList.add('invalid');
        errorMessageElement.textContent = message;
        errorMessageElement.classList.add('visible');
    }

    // Helper function to hide error messages
    function hideError(inputElement, errorMessageElement) {
        inputElement.classList.remove('invalid');
        errorMessageElement.classList.remove('visible');
        errorMessageElement.textContent = ''; // Clear the message
    }

    jsForm.addEventListener('submit', function(event) {
        // Prevent the default form submission
        event.preventDefault();

        let isValid = true;

        // --- Username Validation ---
        const usernameValue = jsUsernameInput.value.trim();
        const usernameError = document.querySelector('.error-message[data-for="js-username"]');
        if (usernameValue === '') {
            showError(jsUsernameInput, usernameError, 'Username is required.');
            isValid = false;
        } else if (usernameValue.length < 4 || usernameValue.length > 20) {
            showError(jsUsernameInput, usernameError, 'Username must be between 4 and 20 characters.');
            isValid = false;
        } else if (!/^[a-zA-Z0-9_]+$/.test(usernameValue)) {
            showError(jsUsernameInput, usernameError, 'Username can only contain letters, numbers, and underscores.');
            isValid = false;
        } else {
            hideError(jsUsernameInput, usernameError);
        }

        // --- Email Validation ---
        const emailValue = jsEmailInput.value.trim();
        const emailError = document.querySelector('.error-message[data-for="js-email"]');
        // Basic email regex
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (emailValue === '') {
            showError(jsEmailInput, emailError, 'Email is required.');
            isValid = false;
        } else if (!emailRegex.test(emailValue)) {
            showError(jsEmailInput, emailError, 'Please enter a valid email address.');
            isValid = false;
        } else {
            hideError(jsEmailInput, emailError);
        }

        // --- Age Validation ---
        const ageValue = jsAgeInput.value.trim();
        const ageError = document.querySelector('.error-message[data-for="js-age"]');
        const age = parseInt(ageValue, 10);

        if (ageValue === '') {
            showError(jsAgeInput, ageError, 'Age is required.');
            isValid = false;
        } else if (isNaN(age)) {
            showError(jsAgeInput, ageError, 'Age must be a number.');
            isValid = false;
        } else if (age < 18 || age > 120) {
            showError(jsAgeInput, ageError, 'Age must be between 18 and 120.');
            isValid = false;
        } else {
            hideError(jsAgeInput, ageError);
        }

        // --- Country Validation ---
        const countryValue = jsCountrySelect.value;
        const countryError = document.querySelector('.error-message[data-for="js-country"]');
        if (countryValue === '') {
            showError(jsCountrySelect, countryError, 'Please select a country.');
            isValid = false;
        } else {
            hideError(jsCountrySelect, countryError);
        }

        // If all checks pass, proceed with submission
        if (isValid) {
            alert('JS Validated Form submitted successfully!');
            jsForm.reset(); // Reset the form
            // In a real app, you'd submit the data via fetch() or other methods.
        }
    });

    // Optional: Add real-time validation feedback as user types
    const inputFields = [
        { id: 'js-username', validate: (val) => {
            if (val === '') return { valid: false, msg: 'Username is required.' };
            if (val.length < 4 || val.length > 20) return { valid: false, msg: 'Username must be between 4 and 20 characters.' };
            if (!/^[a-zA-Z0-9_]+$/.test(val)) return { valid: false, msg: 'Username can only contain letters, numbers, and underscores.' };
            return { valid: true };
        }},
        { id: 'js-email', validate: (val) => {
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            if (val === '') return { valid: false, msg: 'Email is required.' };
            if (!emailRegex.test(val)) return { valid: false, msg: 'Please enter a valid email address.' };
            return { valid: true };
        }},
        { id: 'js-age', validate: (val) => {
            const age = parseInt(val, 10);
            if (val === '') return { valid: false, msg: 'Age is required.' };
            if (isNaN(age)) return { valid: false, msg: 'Age must be a number.' };
            if (age < 18 || age > 120) return { valid: false, msg: 'Age must be between 18 and 120.' };
            return { valid: true };
        }},
        { id: 'js-country', validate: (val) => {
            if (val === '') return { valid: false, msg: 'Please select a country.' };
            return { valid: true };
        }}
    ];

    inputFields.forEach(field => {
        const inputElement = document.getElementById(field.id);
        const errorElement = document.querySelector(`.error-message[data-for="${field.id}"]`);

        if (inputElement.tagName === 'SELECT') {
            inputElement.addEventListener('change', () => {
                const { valid, msg } = field.validate(inputElement.value);
                if (valid) {
                    hideError(inputElement, errorElement);
                } else {
                    showError(inputElement, errorElement, msg);
                }
            });
        } else {
            inputElement.addEventListener('input', () => {
                const { valid, msg } = field.validate(inputElement.value);
                if (valid) {
                    hideError(inputElement, errorElement);
                } else {
                    showError(inputElement, errorElement, msg);
                }
            });
        }
    });
});

Best Practices