HTML attributes catch format errors (covered here); JavaScript adds custom rules and better UX. The professional pattern:
Validate on blur, clear on input
const rules = {
email: (v) => /^\S+@\S+\.\S+$/.test(v) || "Enter a valid email",
password: (v) => v.length >= 8 || "Minimum 8 characters",
confirm: (v) => v === form.password.value || "Passwords don't match",
};
function validate(input) {
const check = rules[input.name];
const result = check ? check(input.value.trim()) : true;
const errEl = input.nextElementSibling; // <small class="error">
errEl.textContent = result === true ? "" : result;
input.classList.toggle("invalid", result !== true);
return result === true;
}
form.querySelectorAll("input").forEach((inp) => {
inp.addEventListener("blur", () => validate(inp)); // judge when leaving
inp.addEventListener("input", () => { // forgive while typing
if (inp.classList.contains("invalid")) validate(inp);
});
});
form.addEventListener("submit", (e) => {
const allValid = [...form.querySelectorAll("input")].every(validate);
if (!allValid) {
e.preventDefault();
form.querySelector(".invalid")?.focus(); // jump to first problem
}
});The UX law
Never validate on every keystroke of an untouched field โ yelling "invalid email" at the first typed letter is hostile. Blur to judge, input to forgive. And keep server-side validation regardless โ client checks are UX, not security.