Use the Constraint Validation API. Set a message on the field with setCustomValidity() when the value is wrong, and clear it with an empty string when the value is right. This example checks that a confirm-email field matches the email field.
<form id="signup">
<input id="email" type="email" name="email" required>
<input id="confirm-email" type="email" name="confirm" required>
<button>Sign up</button>
</form>
<script>
const email = document.querySelector('#email');
const confirmEmail = document.querySelector('#confirm-email');
function checkMatch() {
confirmEmail.setCustomValidity(
confirmEmail.value === email.value ? '' : 'Emails do not match.'
);
}
email.addEventListener('input', checkMatch);
confirmEmail.addEventListener('input', checkMatch);
</script>What this does
A non-empty custom message marks the field as invalid, so the form will not submit. The message is not displayed immediately. The browser shows it when the user attempts to submit, or when you call reportValidity() on the field. The check runs on both inputs, so editing the first field after the second one matches re-flags the mismatch.
Gotchas
Always clear the message. As long as it is not empty, the form will not pass validation, so a branch that never calls setCustomValidity('') leaves the field stuck as invalid even after the user fixes it.
A novalidate attribute on the form turns off interactive validation, so your message will never appear there. Also, this check only runs in the browser, so repeat it on the server before you trust the data.
Back to All Questions