⚡IntermediateAdvanced Forms
novalidate and Custom Validation
Disable native browser validation with novalidate and implement custom logic using the Constraint Validation API.
Overview
The novalidate attribute on <form> disables native browser validation entirely, giving you full control over how validation errors are displayed. Combined with the Constraint Validation API, you can use HTML's built-in validity checks (required, pattern, minlength, etc.) in JavaScript while presenting errors in your own styled UI.
Key Constraint Validation API methods and properties:
- input.validity: a ValidityState object with specific flags (valueMissing, typeMismatch, patternMismatch, etc.)
- input.checkValidity(): returns true/false, fires 'invalid' event if false
- input.reportValidity(): same as checkValidity() but also shows the browser's built-in bubble
- input.setCustomValidity("message"): sets a custom error; empty string clears it
Key Points
- •novalidate on <form> disables all native browser popup validation
- •Constraint Validation API still works with novalidate you read validity in JS
- •input.validity.valueMissing is true when a required field is empty
- •input.validity.typeMismatch is true when the value doesn't match the type (e.g., bad email)
- •input.setCustomValidity("") clears a custom error; any non-empty string sets one
- •The invalid event fires on each invalid field when form submission is attempted
Examples
novalidate with custom error display
<form id="signup" novalidate>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<span class="error" aria-live="polite"></span>
</div>
<div class="field">
<label for="pass">Password (8+ chars)</label>
<input type="password" id="pass" name="password" required minlength="8">
<span class="error" aria-live="polite"></span>
</div>
<button type="submit">Sign Up</button>
</form>
<script>
const form = document.getElementById('signup');
form.addEventListener('submit', (e) => {
e.preventDefault();
let valid = true;
form.querySelectorAll('input').forEach(input => {
const errorSpan = input.nextElementSibling;
if (!input.checkValidity()) {
valid = false;
errorSpan.textContent = input.validationMessage;
input.setAttribute('aria-invalid', 'true');
} else {
errorSpan.textContent = '';
input.removeAttribute('aria-invalid');
}
});
if (valid) form.submit();
});
</script>Custom validity with setCustomValidity
<form novalidate>
<label for="username">Username</label>
<input type="text" id="username" name="username" required>
<label for="confirm">Confirm Username</label>
<input type="text" id="confirm" name="confirm_username">
<button type="submit">Submit</button>
</form>
<script>
const username = document.getElementById('username');
const confirm = document.getElementById('confirm');
confirm.addEventListener('input', () => {
if (confirm.value !== username.value) {
confirm.setCustomValidity('Usernames do not match.');
} else {
confirm.setCustomValidity(''); // Clear error
}
});
</script>setCustomValidity("") must be called to clear a previously set error.
Reading specific validity flags
const input = document.getElementById('email');
input.addEventListener('blur', () => {
const v = input.validity;
if (v.valueMissing) showError('Email is required.');
else if (v.typeMismatch) showError('Please enter a valid email address.');
else if (v.tooShort) showError(`Min ${input.minLength} characters required.`);
else clearError();
});Common Mistakes
- Calling setCustomValidity("message") but never calling setCustomValidity("") when valid the error persists forever
- Using novalidate without implementing any JS validation forms submit with invalid data
- Not associating error messages with inputs via aria-describedby or aria-live for screen readers
Note
The best pattern: use novalidate + the Constraint Validation API (validity flags + setCustomValidity) so you get the benefit of HTML5's built-in rules while controlling the visual presentation completely.
Exercises
Write HTML to solve each exercise. Use Preview to see it render in a live iframe, then mark as done.
Loading editor…
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4