The HTML5 Constraint Validation API: Native Form Validation
Harness browser-native validation without heavy JavaScript: checkValidity(), reportValidity(), setCustomValidity(), validity state flags, and CSS pseudo-classes (:valid, :invalid).
By the end, you’ll be able to…
- Inspect the validity state flags (valueMissing, typeMismatch, patternMismatch, rangeOverflow) on Form Elements
- Display localized, accessible validation error messages using element.setCustomValidity()
- Style real-time validation states cleanly using CSS pseudo-classes (:valid, :invalid, :user-invalid)
01Browser-Native Validation vs Custom JavaScript Libraries
In early web development, verifying that an email had a valid format or that required fields were populated required shipping hundreds of kilobytes of custom JavaScript validation libraries. These scripts often introduced accessibility barriers, failed to support native keyboard focus traps, and degraded performance.
HTML5 introduced the Constraint Validation API directly into the browser engine. By combining declarative HTML attributes (required, minlength, pattern, type="email") with native JavaScript methods, the browser enforces validation automatically.
When a user attempts to submit an invalid form, the browser halts submission, focuses the first invalid input automatically, and displays an accessible native error tooltip localized to the user's operating system language.
<form id="signup-form" novalidate> <label for="username">Username (5-12 alphanumeric):</label> <input type="text" id="username" name="username" required minlength="5" maxlength="12" pattern="[a-zA-Z0-9]+"> <span class="error-msg" id="username-error" aria-live="polite"></span> <button type="submit">Create Account</button></form><script>const form = document.getElementById('signup-form');const username = document.getElementById('username');const errorMsg = document.getElementById('username-error');form.addEventListener('submit', (e) => { // Check native validity state if (!username.checkValidity()) { e.preventDefault(); // Stop submission // Inspect exact failure cause via validity object if (username.validity.valueMissing) { errorMsg.textContent = "Username is strictly required."; } else if (username.validity.tooShort) { errorMsg.textContent = "Username must be at least 5 characters."; } else if (username.validity.patternMismatch) { errorMsg.textContent = "Username can only contain letters and numbers."; } }});</script>02The ValidityState Interface: Exact Failure Reasons
Every form control exposes a validity property returning a ValidityState object. Rather than a simple boolean, ValidityState exposes exact boolean flags detailing why validation failed:
validity.valueMissing: True if the input is required but empty.
validity.typeMismatch: True if the value does not conform to the type (e.g. invalid type="email" or type="url").
validity.patternMismatch: True if the value fails the specified regex pattern.
validity.tooShort / tooLong: True if character length violates minlength or maxlength.
validity.rangeUnderflow / rangeOverflow: True if a number is outside min / max boundaries.
validity.customError: True if setCustomValidity() was called with a non-empty string.
03Custom Messages with setCustomValidity()
While native browser error tooltips are convenient, custom business logic frequently requires domain-specific error messages (e.g., 'Password must contain a special character').
Calling element.setCustomValidity("Custom error message") marks the element as invalid and sets validity.customError to true. The browser will display your custom string in its native tooltip.
Crucially, to clear the error state and allow form submission, you must call element.setCustomValidity("") with an empty string! Passing an empty string resets validity.customError to false.
04Modern CSS Pseudo-Classes: :user-invalid
A common user experience flaw with CSS :invalid is that it styles inputs as red immediately on page load before the user has even touched the field (since required fields are empty on load).
Modern CSS standardizes :user-invalid. The :user-invalid pseudo-class matches ONLY after the user has interacted with the control and moved focus away (blurred), preventing premature error states.
/* Modern UX: Highlight error ONLY after user interacts! */input:user-invalid { border-color: #dc2626; background-color: #fef2f2;}input:user-valid { border-color: #16a34a;}05Production Standards & Real-World Engineering Trade-Offs
In enterprise web architecture, semantic markup is the foundational layer of accessible, inclusive user experiences. When developers rely on generic unstyled divs and custom JavaScript click handlers rather than native HTML elements, they sever the semantic tree exposed to assistive technologies. Screen readers rely entirely on native HTML element roles, states, and properties to build accessible navigation trees.
By employing native HTML5 input types, fieldsets, legends, and the Constraint Validation API, developers offload complex state management and validation logic to the browser engine. Native inputs automatically activate device-optimized touch keyboards on iOS and Android (such as numeric pads for type='number' or email keyboards for type='email'), while native validation tooltips provide accessible error messaging in the user's localized operating system language.
The ideas worth keeping.
- The Constraint Validation API provides declarative, accessible validation without third-party libraries.
- Inspect the validity object to determine exact reasons for validation failures.
- Use setCustomValidity("") to clear errors and :user-invalid for clean, non-intrusive CSS styling.
Make it yours.
Edit the starter, press Preview, and see your page render in the browser preview. Compare with the worked solution when ready.
Exercise 1: Write a JavaScript snippet that validates whe
Write a JavaScript snippet that validates whether a password input matches a confirm-password input using setCustomValidity.
Explore a worked solution
function validatePasswordMatch(passInput, confirmInput) { if (passInput.value !== confirmInput.value) { confirmInput.setCustomValidity("Passwords do not match."); } else { // Essential: empty string clears error state! confirmInput.setCustomValidity(""); }}Call confirmInput.setCustomValidity("") when passwords match to clear the invalid state.
Exercise 2: Why does adding novalidate to a form novalid
Why does adding novalidate to a <form novalidate> enable custom JavaScript validation while preserving the Constraint Validation API?
Explore a worked solution
// The 'novalidate' attribute instructs the browser NOT to display its default popup error tooltips// and not to block submission automatically when the submit button is clicked.// However, the underlying Constraint Validation API (checkValidity(), validity object)// remains fully active in JavaScript, allowing developers to implement custom accessible error messages.novalidate suppresses native popup bubbles without disabling API methods like checkValidity().
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.