W3docs

JavaScript Constraint Validation API

Learn the HTML5 Constraint Validation API in JavaScript: checkValidity, reportValidity, setCustomValidity, the ValidityState object, and custom error messages.

JavaScript is an essential language for web development, enabling dynamic content and enhanced user interaction. A critical aspect of JavaScript in web forms is the HTML5 Constraint Validation API. This guide explores the API in depth — its methods, the ValidityState object, and how to build custom messages and real-time feedback — with practical examples for both novice and experienced developers.

This page builds on working with forms in the DOM and the submit event and method. If you need to read or send the form's data after it validates, see form properties and methods and FormData.

Introduction to HTML5 Constraint Validation API

The HTML5 Constraint Validation API provides native client-side validation of form elements, catching errors before the form is submitted. Constraints are declared right in the HTML through attributes such as required, type="email", min, max, minlength, maxlength, step, and pattern. The browser then enforces them automatically and exposes them to JavaScript so you can customize the experience.

Client-side validation makes forms feel responsive and reduces unnecessary round-trips to the server. It is not a security boundary, however — a user can bypass it entirely by disabling JavaScript or crafting their own request. Always validate again on the server.

The Constraint Validation API surface

Every form-associated element (<input>, <textarea>, <select>, <button>, <fieldset>, and the <form> itself) exposes the same small set of members.

Methods

  • element.checkValidity() — returns true if the element satisfies all its constraints, otherwise false. On failure it also fires an invalid event on the element.
  • element.reportValidity() — like checkValidity(), but additionally displays the browser's native error bubble for the first invalid field. Useful when you want native UI without a custom message.
  • element.setCustomValidity(message) — sets a custom error string. A non-empty string marks the element invalid; an empty string ('') clears the custom error and lets the element be valid again.
  • form.checkValidity() / form.reportValidity() — validate every control in the form at once.

Properties

  • element.validity — a read-only ValidityState object describing why the field is invalid.
  • element.validationMessage — the localized message the browser would show for the current invalid state (empty when valid).
  • element.willValidatetrue if the element will be checked during validation (disabled and readonly fields are skipped).

The ValidityState object

element.validity exposes a boolean for each kind of failure, plus valid:

Propertytrue when…
valueMissinga required field is empty
typeMismatchthe value is the wrong type (e.g. a malformed type="email")
patternMismatchthe value does not match the pattern attribute
tooShort / tooLongthe value is shorter/longer than minlength/maxlength
rangeUnderflow / rangeOverflowa number/date is below min or above max
stepMismatchthe value does not fit the step increment
customErrorsetCustomValidity() was given a non-empty message
validthe field passes all constraints

Inspecting these flags lets you tailor the message to the exact problem:

const input = document.querySelector('#age');
const v = input.validity;

if (v.valueMissing) {
  input.setCustomValidity('Age is required.');
} else if (v.rangeUnderflow) {
  input.setCustomValidity('You must be at least 18.');
} else {
  input.setCustomValidity(''); // clears the custom error
}

Setting Up Your First Validation

Before diving into complex rules, start with the basics: checking that a required field is not empty.

Warning

Adding the novalidate attribute to a form disables the browser's default validation UI. The Constraint Validation API still works in JavaScript — checkValidity() and validity remain accurate — so you can build your own feedback while suppressing the native bubbles.

<form id="registrationForm" novalidate>
    <label for="username">Username:</label>
    <input type="text" id="username" required />
    <button type="submit">Register</button>
    <span id="usernameError" style="color: red;"></span>
    <span id="registerSuccess" style="color: green; display: none;">Registration successful!</span>
</form>

<script>
document.getElementById('registrationForm').addEventListener('submit', function(event) {
    event.preventDefault();
    const input = document.getElementById('username');
    const usernameError = document.getElementById('usernameError');
    const registerSuccess = document.getElementById('registerSuccess');

    if (!input.checkValidity()) {
        usernameError.textContent = 'Username is required.';
        registerSuccess.style.display = 'none'; // Hide success message if visible
    } else {
        usernameError.textContent = ''; // Clear error message
        registerSuccess.textContent = 'Registration successful!';
        registerSuccess.style.display = 'block'; // Show success message
        input.value = ''; // Reset the username input
    }
});
</script>

This code snippet demonstrates the basic setup where the input.checkValidity() check is used to enforce a field as mandatory. The form will trigger an error message if the username field is left empty.

Implementing Custom Validation Messages

Moving beyond the browser's default alert messages, you can create a more integrated user experience by displaying custom error messages within the HTML layout. Here’s how you can implement this:

Please note that the default HTML5 email validation may not require a top-level domain, allowing inputs like w3docs@aol to pass as valid. To ensure email addresses include a domain, we've added a stricter pattern .+@.+\..+ to the email input field. This regex requires at least one period after the @ symbol, more closely matching real-world email formats. (To learn the regex syntax behind patterns, see anchors for string start and end.)

<form id="contactForm" novalidate>
    <label for="email">Email:</label>
    <input type="email" id="email" pattern=".+@.+\..+" required />
    <button type="submit">Submit</button>
    <span id="emailError" style="color: red"></span>
    <span id="successMessage" style="color: green; display: none;">Submission successful!</span>
</form>

<script>
document.getElementById("contactForm").addEventListener("submit", function (event) {
    event.preventDefault(); // Prevent default form submission

    const email = document.getElementById("email");
    const errorMessage = document.getElementById("emailError");
    const successMessage = document.getElementById("successMessage");

    if (!email.checkValidity()) {
        errorMessage.textContent = "Please enter a valid email address, including a domain."; // Display custom error message
        successMessage.style.display = "none"; // Hide success message if visible
    } else {
        errorMessage.textContent = ""; // Clear the error message
        successMessage.textContent = "Submission successful!";
        successMessage.style.display = "block"; // Show success message
        email.value = ""; // Reset the email input
    }
});
</script>

This code improves user experience by providing immediate, inline feedback about the validity of email input.

Enhancing Form Validations with Patterns

Sometimes, more specific validations are necessary, such as verifying that input conforms to a certain pattern. This is commonly used for phone numbers, zip codes, and similar fields. The pattern attribute is implicitly anchored — the entire value must match — so [0-9]{3}-[0-9]{3}-[0-9]{4} accepts 123-456-7890 but rejects 1234567890.

<form id="signupForm" novalidate>
    <label for="phone">Phone (XXX-XXX-XXXX):</label>
    <input type="tel" id="phone" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" required />
    <button type="submit">Sign Up</button>
    <span id="phoneError" style="color:red;"></span>
    <span id="successMessage" style="color:green; display:none;">Submission successful!</span>
</form>

<script>
document.getElementById('signupForm').addEventListener('submit', function(event) {
    const phone = document.getElementById('phone');
    const phoneError = document.getElementById('phoneError');
    const successMessage = document.getElementById('successMessage');

    if (!phone.checkValidity()) {
        phoneError.textContent = 'Please enter a phone number in the format XXX-XXX-XXXX.';
        successMessage.style.display = 'none'; // Hide success message if present
    } else {
        phoneError.textContent = '';
        phone.value = ''; // Reset the input field
        successMessage.textContent = 'Submission successful!';
        successMessage.style.display = 'block'; // Show success message
    }
});
</script>

This example uses the pattern attribute to specify that the phone number must match a specific format, enhancing the data quality collected through the form.

Real-time validation with the ValidityState object

Waiting until submit can feel slow. By listening to the input event and reading the validity flags, you can give feedback as the user types and craft a message for the exact failure:

<form id="profileForm" novalidate>
    <label for="user">Username (3-12 letters/digits):</label>
    <input type="text" id="user" pattern="[A-Za-z0-9]{3,12}" required />
    <span id="userMsg" style="color:red;"></span>
</form>

<script>
const user = document.getElementById('user');
const msg = document.getElementById('userMsg');

user.addEventListener('input', function () {
    const v = user.validity;
    if (v.valueMissing) {
        msg.textContent = 'Username is required.';
    } else if (v.patternMismatch) {
        msg.textContent = 'Use 3-12 letters or digits only.';
    } else {
        msg.textContent = ''; // valid
    }
});
</script>

Here valueMissing and patternMismatch come straight from the ValidityState object, so a single handler reports the precise reason without manually re-implementing the rules.

Combining declarative and custom rules

Some checks — like "passwords must match" — can't be expressed with HTML attributes. Use setCustomValidity() to fold them into the same validation pipeline:

const password = document.getElementById('password');
const confirm = document.getElementById('confirm');

confirm.addEventListener('input', function () {
    if (confirm.value !== password.value) {
        confirm.setCustomValidity('Passwords do not match.');
    } else {
        confirm.setCustomValidity(''); // clear so the field becomes valid
    }
});

Because the custom error participates in checkValidity(), the form's submit handler needs no special case — the mismatched field simply reports as invalid like any other.

Conclusion

The HTML5 Constraint Validation API is a powerful tool for web developers looking to implement client-side form validation. It not only improves the user experience by providing immediate feedback but also reduces the load on the server. By following the examples provided in this guide, you can create more robust, efficient, and user-friendly web forms. For real-time feedback, consider also listening to input or change events alongside the submit handler.

Practice

Practice
Which features are available with the JavaScript Validation API?
Which features are available with the JavaScript Validation API?
Was this page helpful?