Forms & Constraint Validation API
Purana tareeqa: har field pe if/else. Naya tareeqa: checkValidity(), setCustomValidity(), FormData. Browser khud check karta hai, tum bas logic likho.
HTML5 gives you free validation — no JavaScript needed for basic checks. Just add attributes to your form elements and the browser handles the rest.
Core validation attributes:
• required — field must have a value
• minlength / maxlength — character count bounds
• min / max — numeric range (for type="number")
• step — allowed increment for numbers
• pattern — regex pattern matching
• type="email" / type="url" / type="number" — format checking
When the user submits the form, the browser checks all constraints. If any field is invalid, submission is blocked and a native tooltip appears near the field.
<form id="signup">
<input type="text" name="name" required minlength="2">
<input type="email" name="email" required>
<input type="number" name="age" min="18" max="120" step="1">
<input type="text" name="username"
pattern="[a-zA-Z0-9_]{3,15}"
title="3-15 chars: letters, numbers, underscore">
<input type="url" name="website">
<button type="submit">Sign Up</button>
</form>
<!-- Disable built-in validation -->
<form novalidate>
<!-- Browser will not validate -->
</form>
pattern uses regular expressions. The title attribute provides a hint when pattern validation fails. Without a title, the browser shows a generic "Please match the requested format" message.
Browser tooltips work but look different across Chrome, Firefox, Safari — you cannot fully style them with CSS.
Add novalidate attribute (or set form.noValidate = true) to disable all HTML5 validation. Useful when you want full JavaScript control over validation and error display.
The Constraint Validation API gives you programmatic access to validation state. Instead of relying on browser tooltips, you can check validity, read specific error flags, and set custom error messages.
element.validity returns a ValidityState object with boolean flags for each validation rule:
• valueMissing — empty but required
• typeMismatch — wrong format for email/url/number
• patternMismatch — regex pattern failed
• tooShort / tooLong — length bounds violated
• rangeUnderflow / rangeOverflow — number out of min/max range
• stepMismatch — number doesn't match step value
• badInput — browser cannot convert the input
• customError — set via setCustomValidity()
const email = document.querySelector("input[name=email]");
const form = document.getElementById("signup");
email.addEventListener("input", () => {
email.setCustomValidity(""); // Clear first!
if (email.value.endsWith("@tempmail.com")) {
email.setCustomValidity("Temporary emails not allowed.");
}
if (!email.checkValidity()) {
console.log("Invalid!", email.validity);
// validity.valueMissing = true if empty + required
// validity.typeMismatch = true if bad email format
// validity.customError = true if setCustomValidity called
}
});
form.addEventListener("submit", (e) => {
e.preventDefault();
if (!form.checkValidity()) {
form.reportValidity();
return;
}
console.log("Form is valid!");
});
checkValidity() returns true/false with no UI — pure logic. reportValidity() does the same check but ALSO shows the browser's native validation tooltips. Use checkValidity for custom error display, reportValidity when you want browser UI.element.validationMessage returns the current error message string. Empty string if the field is valid. Includes both built-in and custom messages.
Browser tooltips are limited — you can't style them consistently or position them precisely. The solution: custom error display using checkValidity() combined with your own DOM manipulation.
When to validate:
• input event — fires on every keystroke, responsive but can be noisy. Use with debounce for API checks.
• blur event (focusout) — fires when user leaves the field, less intrusive, good for format checks.
• submit event — final check before processing. Always validate here regardless of other events.
function validateField(field) {
field.setCustomValidity(""); // Clear first!
if (field.name === "username" && field.value.includes(" ")) {
field.setCustomValidity("Username cannot contain spaces.");
}
const errorEl = field.parentElement.querySelector(".error-msg");
if (!field.checkValidity()) {
field.classList.add("invalid");
if (errorEl) {
errorEl.textContent = field.validationMessage;
}
return false;
} else {
field.classList.remove("invalid");
if (errorEl) errorEl.textContent = "";
return true;
}
}
form.addEventListener("input", (e) => {
if (e.target.matches("input, textarea, select")) {
validateField(e.target);
}
});
form.addEventListener("focusout", (e) => {
if (e.target.matches("input, textarea, select")) {
validateField(e.target);
}
});
form.addEventListener("submit", (e) => {
e.preventDefault();
let isValid = true;
form.querySelectorAll("input, textarea, select").forEach(field => {
if (!validateField(field)) isValid = false;
});
if (isValid) processForm();
});
setCustomValidity("") before re-validating. The browser does NOT auto-clear custom errors when the user fixes their input. If you don't clear, the field stays invalid forever — a very common bug.FormData is the modern way to collect form data — no manual DOM queries needed. One call extracts everything: text inputs, files, checkboxes, all of it.
new FormData(formElement) collects ALL form fields automatically. No need to read each input individually.
form.addEventListener("submit", (e) => {
e.preventDefault();
const formData = new FormData(form);
// Get individual values
const name = formData.get("name"); // "Sai"
const email = formData.get("email"); // "sai@example.com"
const age = formData.get("age"); // "25" (always string!)
const file = formData.get("avatar"); // File object
// Multiple values (checkboxes)
const skills = formData.getAll("skills"); // ["js", "css", "html"]
// Check if field exists
formData.has("name"); // true
// Iterate all entries
for (const [key, value] of formData.entries()) {
console.log(key, value);
}
// Send with fetch — Content-Type set AUTOMATICALLY!
fetch("/api/users", {
method: "POST",
body: formData // Do NOT set Content-Type!
});
// Add extra data not in the form
formData.append("source", "javascript-course");
});
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXYZ. Setting it manually removes the boundary string, and the server cannot parse the request. Just pass the formData as the body and let the browser handle the header.FormData methods: .get(name) gets first value, .getAll(name) gets all values (for checkboxes), .has(name) checks existence, .append(key, value) adds data, .delete(key) removes, .entries() returns an iterable.
Key detail: formData.get("age") returns "25" (string), not 25 (number). FormData always returns strings for text inputs. Convert with Number() or parseInt() if needed.
Not every validation should happen on every keystroke. Debounced validation waits for the user to stop typing before checking — perfect for API calls like username availability.
Password strength is another real-world pattern: calculate a score based on length, character variety, and common patterns, then show visual feedback as the user types.
// Debounced username availability check
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const checkUsername = debounce(async (username) => {
const res = await fetch("/api/check-username?u=" + username);
const data = await res.json();
const field = document.querySelector("input[name=username]");
if (!data.available) {
field.setCustomValidity("Username already taken!");
} else {
field.setCustomValidity("");
}
validateField(field);
}, 300);
usernameInput.addEventListener("input", (e) => {
checkUsername(e.target.value);
});
// Password strength meter
passwordInput.addEventListener("input", (e) => {
const pwd = e.target.value;
let strength = 0;
if (pwd.length >= 8) strength++;
if (pwd.length >= 12) strength++;
if (/[A-Z]/.test(pwd)) strength++;
if (/[0-9]/.test(pwd)) strength++;
if (/[^A-Za-z0-9]/.test(pwd)) strength++;
const meter = document.getElementById("strength-meter");
meter.className = "strength-" + Math.min(strength, 4);
});
checkValidity() instead — it is purely logical and does not force layout.Confirm password matching: Compare the password and confirm fields in real-time. Use setCustomValidity("Passwords do not match") on the confirm field when they differ, and clear it when they match.
Lo kar liya — Key Points:
- ✅ HTML5 validation attributes (required, minlength, pattern, type) provide free browser-side validation
- ✅ element.validity returns a ValidityState object with boolean flags for each validation rule
- ✅ checkValidity() returns true/false without UI; reportValidity() also shows browser validation tooltips
- ✅ setCustomValidity("message") adds a custom error; setCustomValidity("") clears it — must clear manually
- ✅ Validate on input for real-time feedback, blur for less intrusive checks, and submit for the final gate
- ✅ new FormData(form) collects all form data automatically, including files, and works directly with fetch
- ✅ Don't set Content-Type manually when sending FormData with fetch — the browser sets it with the correct boundary
- ✅ Use debounce for API-based validation (like username availability) to avoid excessive requests
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login