Forms
Forms are the original interactive feature of the web — they collect input from the user and send it to a server. Even in JavaScript-heavy apps, every login, search box, signup and checkout is still a <form> under the hood. Understanding the DOM API for forms — how to read fields, listen for submission, and serialize values — pays off in every project, framework or not.
The form element
A <form> is a container that groups related inputs and offers two behaviours for free:
On submit (a click on a
type="submit"button, or Enter inside a single-line input) it POSTs to itsactionURL.It owns its inputs — you can iterate them through
form.elementsand reset them withform.reset().
login.html
<form id="login" method="post" action="/login">
<label>
Email
<input name="email" type="email" required />
</label>
<label>
Password
<input name="password" type="password" required minlength="8" />
</label>
<button type="submit">Sign in</button>
</form>The submit event
The submit event fires on the form when the user attempts to send it. The default behaviour is the classic full-page POST. In a single-page app you almost always call preventDefault and take over.
handle the submission in JS
const form = document.getElementById("login");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const data = new FormData(form);
const res = await fetch("/login", { method: "POST", body: data });
if (res.ok) location.assign("/dashboard");
else alert("Login failed");
});FormData — read every field at once
new FormData(form) walks every input that has a name attribute and collects values into a structure you can iterate, append to, or pass directly to fetch.
const data = new FormData(form);
data.get("email"); // "user@example.com"
data.getAll("topics"); // every checked checkbox with name="topics"
for (const [name, value] of data) {
console.log(name, value);
}
// turn it into a plain object
const obj = Object.fromEntries(data);email user@example.com password hunter2
Reading and writing individual fields
Every input is reachable via form.elements.<name> (or form.<name> for short). For most types, .value is the property you want.
form.email.value; // current value form.email.value = "x@y.z"; // set it programmatically form.email.focus(); // focus the field form.email.disabled = true;
Checkboxes and radios use .checked. <select> uses .value for single-select, .selectedOptions for multi-select. A <textarea> is just an input with .value.
value vs defaultValue
This trips people up. input.value is the current value — what the user has typed. input.defaultValue is the value that was in the value attribute when the HTML was parsed.
<input id="q" value="hello" />
q.value; // "hello" (initially) q.defaultValue; // "hello" q.value = "world"; q.value; // "world" q.defaultValue; // still "hello" form.reset(); // sets q.value back to "hello"
The same split exists for checkboxes — checked vs defaultChecked — and for select options — selected vs defaultSelected.
The input event vs change
Inputs emit two related events:
inputfires on every keystroke (or every change to a checkbox / select). Use it for live validation, character counters, search-as-you-type.changefires when the value is committed — usually on blur for text fields, immediately for checkboxes and selects. Use it for "save when the user finishes editing".
form.email.addEventListener("input", () => {
counter.textContent = form.email.value.length;
});
form.country.addEventListener("change", () => {
loadCities(form.country.value);
});Controlled-style updates in vanilla JS
React popularised the term controlled input, but the pattern works fine without a framework: keep your model in JavaScript, update the DOM on input events, and read from the model — not the DOM — when you submit.
single source of truth
const state = { query: "", sort: "asc" };
document.querySelector("#q").addEventListener("input", (e) => {
state.query = e.target.value;
render();
});
document.querySelector("#sort").addEventListener("change", (e) => {
state.sort = e.target.value;
render();
});
function render() {
results.textContent = JSON.stringify(state);
}Resetting and disabling
form.reset(); // back to defaultValue / defaultChecked
form.querySelector("button").disabled = true; // prevent double submit
form.elements.email.readOnly = true; // visible but not editableForm-flavoured checklist
Wrap inputs in a
<form>so Enter submits andFormDataworks.Always give inputs a
name— that is whatFormDataand the server use.Listen for
submiton the form, notclickon the button.Use
inputfor live updates,changefor "committed" updates.Use
valuefor the current state,defaultValuefor whatreset()returns to.