Forms
Learn how to handle form input, validation, and submission using JavaScript and the DOM.
Forms
Forms are how users send data to your application — login credentials, search queries, contact messages, settings. JavaScript gives you full control over every part of a form — reading values, validating input, and handling submission.
The HTML We Will Use
<form id="signup-form">
<input type="text" id="username" placeholder="Username">
<input type="email" id="email" placeholder="Email">
<input type="password" id="password" placeholder="Password">
<select id="role">
<option value="">Select a role</option>
<option value="student">Student</option>
<option value="developer">Developer</option>
<option value="designer">Designer</option>
</select>
<textarea id="bio" placeholder="Short bio..."></textarea>
<label>
<input type="checkbox" id="terms"> I agree to the terms
</label>
<button type="submit">Sign Up</button>
</form>Reading Form Values
Every form element has a value property that holds its current value.
const username = document.getElementById("username");
const email = document.getElementById("email");
const role = document.getElementById("role");
const bio = document.getElementById("bio");
const terms = document.getElementById("terms");
// Text inputs, email, password, textarea — use .value
console.log(username.value); // whatever the user typed
console.log(email.value); // email@example.com
console.log(bio.value); // multi-line text
// Select — .value gives the selected option's value
console.log(role.value); // "student", "developer", or "designer"
// Checkbox — use .checked, not .value
console.log(terms.checked); // true or falseRadio buttons
Radio buttons are grouped by name. Check .checked on each one or loop through them:
const radios = document.querySelectorAll('input[name="gender"]');
let selected;
for (const radio of radios) {
if (radio.checked) {
selected = radio.value;
break;
}
}
console.log(selected); // "male", "female", etc.Handling Form Submission
Listen for the submit event on the form — not a click on the button. The submit event fires when the form is submitted by any means — button click, pressing Enter in an input.
Always call event.preventDefault() to stop the page from refreshing.
const form = document.getElementById("signup-form");
form.addEventListener("submit", (e) => {
e.preventDefault(); // stop page refresh
const data = {
username: document.getElementById("username").value.trim(),
email: document.getElementById("email").value.trim(),
password: document.getElementById("password").value,
role: document.getElementById("role").value,
bio: document.getElementById("bio").value.trim(),
terms: document.getElementById("terms").checked
};
console.log(data);
// { username: "ali_dev", email: "ali@example.com", ... }
});Using FormData
FormData is a built-in object that collects all form values automatically — no need to select each input manually. Every input needs a name attribute for this to work.
<form id="signup-form">
<input type="text" name="username" placeholder="Username">
<input type="email" name="email" placeholder="Email">
<input type="password" name="password" placeholder="Password">
<button type="submit">Sign Up</button>
</form>form.addEventListener("submit", (e) => {
e.preventDefault();
const formData = new FormData(form);
// Get a single value
console.log(formData.get("username")); // "ali_dev"
console.log(formData.get("email")); // "ali@example.com"
// Convert to a plain object
const data = Object.fromEntries(formData);
console.log(data);
// { username: "ali_dev", email: "ali@example.com", password: "..." }
});Object.fromEntries(formData) turns all form fields into a clean object in one line. This is the most efficient way to collect form data.
Listening to Input Changes
React to user input as it happens — not just on submit.
const usernameInput = document.getElementById("username");
// Fires on every keystroke
usernameInput.addEventListener("input", (e) => {
console.log(e.target.value); // current value as user types
});
// Fires when input loses focus
usernameInput.addEventListener("blur", () => {
validateUsername(usernameInput.value);
});
// Fires when value changes and element loses focus
usernameInput.addEventListener("change", (e) => {
console.log("Final value:", e.target.value);
});Use input for live feedback as the user types. Use blur for validation when they leave a field. Use change for select dropdowns and checkboxes.
Form Validation
Validating input before submission is one of the most important jobs JavaScript does with forms.
Manual validation
function validateForm(data) {
const errors = {};
if (!data.username) {
errors.username = "Username is required.";
} else if (data.username.length < 3) {
errors.username = "Username must be at least 3 characters.";
} else if (data.username.includes(" ")) {
errors.username = "Username cannot contain spaces.";
}
if (!data.email) {
errors.email = "Email is required.";
} else if (!data.email.includes("@")) {
errors.email = "Please enter a valid email.";
}
if (!data.password) {
errors.password = "Password is required.";
} else if (data.password.length < 8) {
errors.password = "Password must be at least 8 characters.";
}
if (!data.role) {
errors.role = "Please select a role.";
}
if (!data.terms) {
errors.terms = "You must agree to the terms.";
}
return errors;
}Showing errors in the UI
function showErrors(errors) {
// Clear previous errors
document.querySelectorAll(".error-message").forEach(el => el.remove());
document.querySelectorAll(".input-error").forEach(el => {
el.classList.remove("input-error");
});
// Show new errors
for (const [field, message] of Object.entries(errors)) {
const input = document.getElementById(field);
if (!input) continue;
input.classList.add("input-error");
const errorEl = document.createElement("span");
errorEl.className = "error-message";
errorEl.textContent = message;
input.insertAdjacentElement("afterend", errorEl);
}
}Putting it together
form.addEventListener("submit", (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(form));
data.terms = document.getElementById("terms").checked;
const errors = validateForm(data);
if (Object.keys(errors).length > 0) {
showErrors(errors);
return; // stop — do not submit
}
// No errors — proceed
console.log("Form is valid. Submitting...", data);
submitForm(data);
});Live Validation — Validating as User Types
Better UX means validating each field as the user interacts with it — not all at once on submit.
const usernameInput = document.getElementById("username");
const emailInput = document.getElementById("email");
const passwordInput = document.getElementById("password");
function showFieldError(input, message) {
clearFieldError(input);
input.classList.add("input-error");
const error = document.createElement("span");
error.className = "error-message";
error.textContent = message;
input.insertAdjacentElement("afterend", error);
}
function clearFieldError(input) {
input.classList.remove("input-error");
const existing = input.nextElementSibling;
if (existing?.classList.contains("error-message")) {
existing.remove();
}
}
function showFieldSuccess(input) {
clearFieldError(input);
input.classList.add("input-success");
}
// Validate username on blur
usernameInput.addEventListener("blur", () => {
const value = usernameInput.value.trim();
if (!value) {
showFieldError(usernameInput, "Username is required.");
} else if (value.length < 3) {
showFieldError(usernameInput, "At least 3 characters.");
} else {
showFieldSuccess(usernameInput);
}
});
// Live password strength indicator
passwordInput.addEventListener("input", () => {
const value = passwordInput.value;
const strength = value.length >= 12 ? "Strong"
: value.length >= 8 ? "Medium"
: "Weak";
let indicator = document.getElementById("pwd-strength");
if (!indicator) {
indicator = document.createElement("span");
indicator.id = "pwd-strength";
passwordInput.insertAdjacentElement("afterend", indicator);
}
indicator.textContent = `Strength: ${strength}`;
indicator.className = strength.toLowerCase();
});Resetting a Form
// Reset all fields to their default values
form.reset();
// Or reset programmatically after submission
form.addEventListener("submit", (e) => {
e.preventDefault();
// ... handle submission
form.reset();
console.log("Form cleared.");
});Disabling and Enabling Inputs
const submitBtn = document.querySelector('button[type="submit"]');
const inputs = form.querySelectorAll("input, select, textarea");
// Disable everything while submitting
function setFormLoading(loading) {
submitBtn.disabled = loading;
submitBtn.textContent = loading ? "Submitting..." : "Sign Up";
inputs.forEach(input => {
input.disabled = loading;
});
}A Real Example — Complete Signup Form
const form = document.getElementById("signup-form");
form.addEventListener("submit", async (e) => {
e.preventDefault();
// Collect data
const formData = new FormData(form);
const data = Object.fromEntries(formData);
data.terms = document.getElementById("terms").checked;
// Validate
const errors = validateForm(data);
if (Object.keys(errors).length > 0) {
showErrors(errors);
return;
}
// Show loading state
setFormLoading(true);
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));
console.log("Account created!", data);
// Show success message
form.innerHTML = `
<div class="success">
<h2>Welcome, ${data.username}!</h2>
<p>Your account has been created successfully.</p>
</div>
`;
} catch (error) {
console.error("Submission failed:", error);
setFormLoading(false);
}
});The async/await syntax used here is covered in full detail in the Async JavaScript section. For now just know it handles operations that take time — like sending data to a server.
Summary
- Listen for
submiton the form — notclickon the button - Always call
e.preventDefault()to stop page refresh - Read input values with
.value— checkboxes use.checked FormData+Object.fromEntries()collects all form values in one line- Use
inputevent for live feedback,blurfor field validation,changefor selects - Validate before submitting — return early if errors exist
- Show errors next to the relevant fields, clear them when the user corrects input
- Disable the form during submission to prevent double submits
form.reset()clears all fields back to their default values