JavaScriptForms

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 its action URL.

  • It owns its inputs — you can iterate them through form.elements and reset them with form.reset().

login.html

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

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.

JS
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
Files travel for free
If the form has `<input type="file">`, the corresponding FormData entry is a `File` object. Passing FormData to `fetch` automatically sets `multipart/form-data` and streams the files.
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.

JS
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.

HTML
<input id="q" value="hello" />

JS
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:

  • input fires on every keystroke (or every change to a checkbox / select). Use it for live validation, character counters, search-as-you-type.

  • change fires 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".

JS
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

JS
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

JS
form.reset();                   // back to defaultValue / defaultChecked
form.querySelector("button").disabled = true; // prevent double submit
form.elements.email.readOnly = true;          // visible but not editable
Form-flavoured checklist
  • Wrap inputs in a <form> so Enter submits and FormData works.

  • Always give inputs a name — that is what FormData and the server use.

  • Listen for submit on the form, not click on the button.

  • Use input for live updates, change for "committed" updates.

  • Use value for the current state, defaultValue for what reset() returns to.