setCustomValidity() bridges the gap between declarative HTML constraints and business logic that requires JavaScript to evaluate, while keeping the same native, accessible validation UI covered throughout this module.
1Injecting A Custom Validation Error
element.setCustomValidity('some message') marks that field invalid with the given message, fully integrating with everything covered earlier in this module: checkValidity() and reportValidity() reflect it, native form submission is blocked exactly as it would be for a failed required or pattern constraint, and the message appears in the same native error UI bubble.
This is the correct mechanism for business-rule validation no declarative attribute can express โ 'this email is already registered' (requiring a server round-trip to know), 'password confirmation doesn't match another field's value', or any other JavaScript-evaluated condition.
2The Critical Clearing Requirement
Unlike built-in constraints such as required or pattern, which the browser continuously and automatically re-evaluates against the field's current value, a custom validity message set via setCustomValidity() persists indefinitely โ even after the underlying condition that caused it is resolved โ until explicitly cleared by calling setCustomValidity('') with an empty string.
Forgetting this step is one of the most common bugs in custom validation implementations: a field remains permanently 'stuck' invalid even after the user has correctly fixed the actual problem, because nothing ever called the clearing method.
3The Standard Real-Time Re-Validation Pattern
Given the explicit-clearing requirement, the reliable, standard implementation pattern re-runs the full check-and-set (or check-and-clear) logic on every relevant input event, rather than only checking once at some earlier point. This way, the custom validity state is always recomputed fresh against the field's current live value, correctly setting or clearing the custom message as needed on every keystroke.
This pattern generalizes cleanly to any custom business rule: compute the condition, call setCustomValidity(message) if invalid or setCustomValidity('') if valid, and attach that logic to the input event of every field the rule depends on.
4Step-by-Step Breakdown
When Built-In Constraints Aren't Enough. required, pattern, and range constraints cover a lot, but not everything โ 'this username is already taken' or 'password confirmation doesn't match' are business rules no declarative attribute can express. setCustomValidity() injects exactly this kind of custom error into the same native validation system covered throughout this module.
setCustomValidity() Marks A Field Invalid With A Custom Message. Calling element.setCustomValidity('Passwords do not match') marks that field invalid with the given message, integrating fully with checkValidity(), reportValidity(), and native form submission blocking โ as if it were a built-in constraint.
setCustomValidity() Effect. After calling confirmPassword.setCustomValidity('Passwords do not match'), what happens if the form is submitted?
- โNothing; setCustomValidity() has no effect on submission
- โSubmission is blocked, showing that custom message in the native error UI
- โSubmission proceeds; the developer must manually check and block it separately
Must Be Explicitly Cleared With An Empty String. A field marked invalid via setCustomValidity() stays invalid until explicitly cleared by calling setCustomValidity('') with an empty string โ it does not automatically clear itself when the underlying condition is fixed, a frequent source of bugs.
Clearing Custom Validity. If a password mismatch is fixed by the user, does the field's custom validity automatically clear itself?
- โYes, it automatically re-evaluates and clears on every keystroke
- โNo, setCustomValidity('') must be explicitly called to clear it
- โIt only clears automatically at the moment of form submission
Best Paired With Real-Time Re-Validation. Since custom validity doesn't auto-clear, the standard pattern re-runs the check-and-set logic on every relevant input event, so the field's validity state stays accurately in sync with the user's live typing, not just the state at some earlier check.
Real-Time Custom Validation. Why is it standard practice to re-run setCustomValidity() logic on every 'input' event, rather than just once?
- โIt's purely a performance optimization with no functional necessity
- โIt keeps the custom validity state accurately synced with the user's current live input
- โsetCustomValidity() literally cannot be called more than once without this pattern
Custom Validation Integrated. You now know how to inject custom business-rule validation errors into the native Constraint Validation API with setCustomValidity(), why it must be explicitly cleared, and the standard real-time re-checking pattern that keeps validity state accurately synced โ completing this module's core validation trilogy.
Attach A Custom Error Message. A data attribute can hold a custom message for a script to display on invalid input.
Level Up ๐
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Custom Validity Messages Inherit The Same Accessible Native Error UI As Built-In Constraints
Since setCustomValidity() integrates fully with the Constraint Validation API, custom business-rule errors get the same automatic focus management and screen-reader announcement as native constraint failures, with no additional accessibility work required.
SEO Implications
- 1
Native Custom Validation Avoids The Need For A Separate JavaScript Error-Rendering UI System
Reusing the browser's built-in, accessible error UI for business-rule validation avoids shipping and maintaining redundant custom error-display components, indirectly benefiting bundle size and page performance.
Best Practices
Always Re-Run setCustomValidity() Logic On Relevant input Events, Never Just Once
Since custom validity doesn't auto-clear, this is the only reliable way to ensure the field's validity state accurately reflects its current value at all times, not a stale earlier check.
Reserve setCustomValidity() For Genuine Business Rules Declarative Attributes Can't Express
For anything expressible via required, pattern, min/max, or the other constraints from earlier lessons, prefer those simpler, fully-declarative approaches first.
Frequent Bugs
A field remains permanently invalid even after the user has correctly fixed the underlying issue.
The custom validity was never explicitly cleared. Add setCustomValidity('') logic that runs whenever the condition becomes satisfied.
A custom validation check only runs once on page load and never reflects the user's subsequent typing.
Attach the check-and-set/clear logic to the field's 'input' event so it re-evaluates on every relevant change.
Real-World Examples
Password Confirmation Matching
A signup form using setCustomValidity() to enforce that two password fields match, with correct clearing behavior.
function validateMatch() {
confirmPassword.setCustomValidity(
confirmPassword.value === password.value ? '' : 'Passwords do not match'
);
}
password.addEventListener('input', validateMatch);
confirmPassword.addEventListener('input', validateMatch);