Forms & Form Events
Checking access...
Forms are how users submit data to your application. JavaScript lets you intercept form submissions, validate data, and provide real-time feedback without a page reload.
Basic Form Handling
<form id="signup-form"> <div> <label for="name">Name:</label> <input type="text" id="name" name="name" required /> </div> <div> <label for="email">Email:</label> <input type="email" id="email" name="email" required /> </div> <button type="submit">Sign Up</button></form>
<div id="message"></div>const form = document.getElementById("signup-form");const message = document.getElementById("message");
form.addEventListener("submit", (event) => { event.preventDefault(); // prevent page reload
const formData = new FormData(form); const data = Object.fromEntries(formData);
message.textContent = `Welcome, ${data.name}! Check your email at ${data.email}.`;});FormData API
FormData provides a clean way to collect form values:
form.addEventListener("submit", (event) => { event.preventDefault();
const formData = new FormData(form);
// Get individual values console.log(formData.get("name")); // "Alice" console.log(formData.get("email")); // "alice@example.com"
// Check if a field exists console.log(formData.has("name")); // true
// Get all values for a field (checkboxes, multi-select) formData.getAll("hobbies"); // ["reading", "coding"]
// Iterate all entries for (const [key, value] of formData) { console.log(key, value); }
// Convert to plain object const data = Object.fromEntries(formData); console.log(data); // { name: "Alice", email: "alice@example.com" }
// Send as JSON fetch("/api/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), });});Form Events
Submit Event
Fires when the user clicks a submit button or presses Enter in a form field:
form.addEventListener("submit", (event) => { event.preventDefault(); // process form data});Input Event
Fires on every value change — ideal for real-time validation and live previews:
const nameInput = document.getElementById("name");const preview = document.getElementById("preview");
nameInput.addEventListener("input", () => { preview.textContent = nameInput.value || "Your name will appear here";});Change Event
Fires when the value changes and the input loses focus (or for checkboxes/radios, immediately on change):
emailInput.addEventListener("change", () => { // Fires when user finishes editing and clicks away validateEmail(emailInput.value);});
selectInput.addEventListener("change", () => { console.log("Selected:", selectInput.value);});Focus and Blur
input.addEventListener("focus", () => { input.parentElement.classList.add("focused");});
input.addEventListener("blur", () => { input.parentElement.classList.remove("focused"); // Good time to validate (after user finishes) validateField(input);});Input Validation
HTML5 Built-in Validation
<form> <input type="text" required minlength="2" maxlength="50" /> <input type="email" required /> <input type="number" min="18" max="120" /> <input type="url" /> <input pattern="[A-Za-z]+" title="Letters only" /></form>Check validity with the Constraint Validation API:
const input = document.getElementById("name");
// Check validityconsole.log(input.validity.valid); // true/falseconsole.log(input.validity.tooShort);console.log(input.validity.valueMissing);console.log(input.validationMessage); // browser's error message
// Custom validationinput.setCustomValidity(""); // clear custom errorinput.setCustomValidity("This name is already taken"); // set error
// Report validity visuallyinput.reportValidity();Custom Validation with JavaScript
const form = document.getElementById("signup-form");const emailInput = document.getElementById("email");const passwordInput = document.getElementById("password");const errors = document.getElementById("errors");
function validateField(input) { const error = input.parentElement.querySelector(".error"); if (!error) return true;
if (input.validity.valid) { error.textContent = ""; input.classList.remove("invalid"); return true; }
// Custom error messages if (input.validity.valueMissing) { error.textContent = `${input.name} is required`; } else if (input.validity.typeMismatch) { error.textContent = `Please enter a valid ${input.type}`; } else if (input.validity.tooShort) { error.textContent = `Minimum ${input.minLength} characters required`; }
input.classList.add("invalid"); return false;}
form.addEventListener("submit", (event) => { event.preventDefault();
// Validate all fields const inputs = form.querySelectorAll("input, select, textarea"); let isValid = true;
inputs.forEach((input) => { if (!validateField(input)) { isValid = false; } });
if (isValid) { const formData = new FormData(form); console.log("Form submitted:", Object.fromEntries(formData)); }});
// Real-time validationemailInput.addEventListener("blur", () => validateField(emailInput));passwordInput.addEventListener("input", () => validateField(passwordInput));Real-Time Validation Pattern
<form id="password-form"> <div class="field"> <label for="password">Password</label> <input type="password" id="password" name="password" minlength="8" required /> <ul class="password-requirements"> <li id="req-length" class="invalid">At least 8 characters</li> <li id="req-number" class="invalid">Contains a number</li> <li id="req-upper" class="invalid">Contains uppercase letter</li> </ul> </div></form>const passwordInput = document.getElementById("password");
passwordInput.addEventListener("input", () => { const value = passwordInput.value;
toggleRequirement("req-length", value.length >= 8); toggleRequirement("req-number", /\d/.test(value)); toggleRequirement("req-upper", /[A-Z]/.test(value));
if (passwordInput.validity.valid) { passwordInput.classList.add("valid"); passwordInput.classList.remove("invalid"); } else { passwordInput.classList.remove("valid"); passwordInput.classList.add("invalid"); }});
function toggleRequirement(id, met) { const el = document.getElementById(id); el.className = met ? "valid" : "invalid";}.valid { color: green; }.valid::before { content: "✓ "; }.invalid { color: #999; }.invalid::before { content: "✗ "; }Working with Different Input Types
Checkboxes
<label><input type="checkbox" name="newsletter" value="weekly" checked /> Weekly Newsletter</label><label><input type="checkbox" name="newsletter" value="daily" /> Daily Digest</label>// Single checkbox (boolean)const agree = document.getElementById("agree");console.log(agree.checked); // true/false
// Multiple checkboxesform.addEventListener("submit", (e) => { e.preventDefault(); const formData = new FormData(form); const newsletters = formData.getAll("newsletter"); console.log("Selected:", newsletters); // ["weekly", "daily"]});Radio Buttons
<label><input type="radio" name="plan" value="free" checked /> Free</label><label><input type="radio" name="plan" value="pro" /> Pro</label><label><input type="radio" name="plan" value="enterprise" /> Enterprise</label>form.addEventListener("submit", (e) => { e.preventDefault(); const plan = new FormData(form).get("plan"); console.log("Selected plan:", plan); // "free", "pro", or "enterprise"});Select Dropdowns
<select name="country" id="country"> <option value="">Select a country</option> <option value="US">United States</option> <option value="UK">United Kingdom</option> <option value="JP">Japan</option></select>
<select name="skills" multiple> <option value="js">JavaScript</option> <option value="py">Python</option> <option value="rb">Ruby</option></select>// Single selectconsole.log(document.getElementById("country").value); // "US"
// Multiple selectconst skills = new FormData(form).getAll("skills");console.log(skills); // ["js", "py"]Controlled Form Pattern
Collect and manage form state in a JavaScript object:
const formState = { name: "", email: "", password: "", newsletter: false, plan: "free",};
const form = document.getElementById("signup-form");
// Sync state with form on every inputform.addEventListener("input", (event) => { const { name, value, type, checked } = event.target; formState[name] = type === "checkbox" ? checked : value; console.log("Current state:", formState);});
form.addEventListener("submit", (event) => { event.preventDefault(); console.log("Final data:", formState); // Submit formState to API});Form Reset
// Reset to initial valuesform.reset();
// Clear all fields manuallyform.querySelectorAll("input").forEach((input) => { if (input.type === "checkbox" || input.type === "radio") { input.checked = false; } else { input.value = ""; }});Quick Reference
| Event | Fires When | Use Case |
|---|---|---|
submit | Form is submitted | Process form data |
input | Value changes (every keystroke) | Real-time validation, live preview |
change | Value committed (focus lost) | Final validation |
focus | Element gains focus | Highlight active field |
blur | Element loses focus | Validate on completion |
reset | Form is reset | Clear UI state |
Practice Exercises
Registration form: Build a complete registration form with name, email, password, and confirm password. Validate that passwords match, email is valid, and name is not empty. Show inline error messages.
Dynamic field builder: Create a form where selecting a “category” from a dropdown dynamically shows/hides additional fields (e.g., selecting “Company” shows a company name field).
Character counter: Add a character counter below a
<textarea>that updates in real-time, turns red when near the limit (90%), and prevents submission if over the maxlength.