Skip to main content

Form Properties and Methods in JavaScript

Forms are one of the primary ways users interact with web applications. Every login page, search bar, checkout process, and settings panel relies on forms to collect user input. JavaScript provides a rich API for working with forms: accessing form elements, reading and setting values, handling selections, submitting data programmatically, and collecting all form data at once.

The DOM gives forms special treatment. Forms and their elements have named shortcuts, dedicated properties, and specialized collections that make them easier to work with than generic DOM elements. Understanding these form-specific APIs lets you build dynamic forms, validate input in real time, and submit data without page reloads.

This guide covers the complete form API, from accessing forms and their elements to programmatic submission and the powerful FormData object.

Accessing Forms: document.forms and Named Access

The document.forms property returns a live collection of all <form> elements in the document. You can access forms by index or by name.

By Index

// First form on the page
const firstForm = document.forms[0];

// Second form
const secondForm = document.forms[1];

// Total number of forms
console.log(document.forms.length);

By Name or ID

If a form has a name or id attribute, you can access it directly:

<form name="login">
<input type="text" name="username">
<input type="password" name="password">
</form>

<form id="signup">
<input type="text" name="email">
</form>
// Access by name attribute
const loginForm = document.forms.login;
// or
const loginForm2 = document.forms["login"];

// Access by id attribute
const signupForm = document.forms.signup;
// or
const signupForm2 = document.forms["signup"];

console.log(loginForm); // <form name="login">
console.log(signupForm); // <form id="signup">

document.forms Is a Live Collection

The collection updates automatically when forms are added or removed:

console.log(document.forms.length);   // 2

const newForm = document.createElement("form");
newForm.name = "settings";
document.body.appendChild(newForm);

console.log(document.forms.length); // 3
console.log(document.forms.settings); // <form name="settings">
info

document.forms returns an HTMLCollection, which is live and ordered by document position. While querySelector("form") also finds forms, document.forms provides convenient named access that querySelector does not.

Accessing Form Elements: form.elements and Named Access

Every form has an elements property that returns a live collection of all form controls (inputs, selects, textareas, buttons) inside it.

The elements Collection

<form name="profile">
<input type="text" name="firstName" value="Alice">
<input type="text" name="lastName" value="Smith">
<input type="email" name="email" value="alice@example.com">
<select name="role">
<option value="admin">Admin</option>
<option value="user" selected>User</option>
</select>
<textarea name="bio">Hello world</textarea>
<button type="submit">Save</button>
</form>
const form = document.forms.profile;

// Access by index
console.log(form.elements[0]); // <input name="firstName">
console.log(form.elements[1]); // <input name="lastName">

// Access by name
console.log(form.elements.firstName); // <input name="firstName">
console.log(form.elements["email"]); // <input name="email">
console.log(form.elements.role); // <select name="role">
console.log(form.elements.bio); // <textarea name="bio">

// Total number of form controls
console.log(form.elements.length); // 6 (including the button)

Shorthand: Direct Property Access on the Form

Form elements are also accessible directly as properties on the form element itself, using their name attribute:

const form = document.forms.profile;

// These are equivalent:
console.log(form.elements.firstName); // <input name="firstName">
console.log(form.firstName); // <input name="firstName"> (shorthand)

console.log(form.elements.email); // <input name="email">
console.log(form.email); // <input name="email"> (shorthand)

The shorthand form.name is convenient but can conflict with existing form properties. For example, form.action returns the form's action attribute, not an element named "action". Use form.elements.action to be safe:

// If you have an input named "action"
// <input name="action" value="login">

console.log(form.action); // "http://..." (the form's action URL, NOT the input9
console.log(form.elements.action); // <input name="action"> (the actual input element9

Elements with the Same Name

When multiple elements share the same name (common with radio buttons and checkboxes), form.elements[name] returns a live RadioNodeList (a collection):

<form name="survey">
<input type="radio" name="color" value="red"> Red
<input type="radio" name="color" value="blue" checked> Blue
<input type="radio" name="color" value="green"> Green

<input type="checkbox" name="features" value="wifi"> WiFi
<input type="checkbox" name="features" value="parking" checked> Parking
<input type="checkbox" name="features" value="pool"> Pool
</form>
const form = document.forms.survey;

// Radio buttons with the same name
const colors = form.elements.color;
console.log(colors.length); // 3
console.log(colors[0]); // <input type="radio" value="red">
console.log(colors[1]); // <input type="radio" value="blue">
console.log(colors.value); // "blue" (the value of the checked radio9

// Checkboxes with the same name
const features = form.elements.features;
console.log(features.length); // 3

Fieldset Elements

<fieldset> elements also have their own elements property, containing only the controls within that fieldset:

<form name="account">
<fieldset name="personal">
<input name="name" type="text">
<input name="email" type="email">
</fieldset>
<fieldset name="preferences">
<select name="theme">
<option>Light</option>
<option>Dark</option>
</select>
<input name="notifications" type="checkbox">
</fieldset>
</form>
const form = document.forms.account;

// All form elements
console.log(form.elements.length); // 4

// Only elements in the "personal" fieldset
const personal = form.elements.personal;
console.log(personal.elements.length); // 2
console.log(personal.elements.name); // <input name="name">
console.log(personal.elements.email); // <input name="email">

// Elements in "preferences"
const prefs = form.elements.preferences;
console.log(prefs.elements.length); // 2

Back-Reference: element.form

Every form control has a form property that points back to its parent form:

const emailInput = document.forms.profile.elements.email;
console.log(emailInput.form); // <form name="profile">
console.log(emailInput.form === document.forms.profile); // true

This is useful when you have a reference to an element and need to find its form, or access sibling form elements:

function handleInput(event) {
const input = event.target;
const form = input.form; // Get the parent form
const submitBtn = form.querySelector('[type="submit"]');

// Enable submit only if email is valid
submitBtn.disabled = !form.elements.email.validity.valid;
}

input.value, textarea.value, select.value

Reading and setting values on form controls is straightforward but has important differences between element types.

Text Inputs

<input type="text" name="username" value="initial value">
<input type="password" name="password">
<input type="email" name="email" value="user@example.com">
<input type="number" name="age" value="25">
<input type="hidden" name="token" value="abc123">
const form = document.forms[0];

// Reading values
console.log(form.elements.username.value); // "initial value"
console.log(form.elements.password.value); // "" (empty)
console.log(form.elements.email.value); // "user@example.com"
console.log(form.elements.age.value); // "25" (always a string!)
console.log(form.elements.token.value); // "abc123"

// Setting values
form.elements.username.value = "newUser";
form.elements.age.value = "30";

// For numbers, value is always a string (use valueAsNumber9
console.log(form.elements.age.value); // "30" (string)
console.log(form.elements.age.valueAsNumber); // 30 (number)
warning

The .value property of <input type="number"> is always a string, not a number. Use .valueAsNumber to get the numeric value, or parse it with Number() or parseInt().

const ageInput = form.elements.age;
ageInput.value = "25";

console.log(typeof ageInput.value); // "string"
console.log(typeof ageInput.valueAsNumber); // "number"

// valueAsNumber is NaN if the field is empty or invalid
ageInput.value = "";
console.log(ageInput.valueAsNumber); // NaN

Checkboxes and Radio Buttons

For checkboxes and radios, .value returns the value attribute (like "on" if no value is set). Use .checked to read or set whether it is selected:

<input type="checkbox" name="agree" value="yes">
<input type="checkbox" name="newsletter" checked>

<input type="radio" name="plan" value="free">
<input type="radio" name="plan" value="pro" checked>
<input type="radio" name="plan" value="enterprise">
const form = document.forms[0];

// Checkboxes
const agree = form.elements.agree;
console.log(agree.value); // "yes" (the value attribute)
console.log(agree.checked); // false (not checked)

const newsletter = form.elements.newsletter;
console.log(newsletter.value); // "on" (default value when none specified)
console.log(newsletter.checked); // true (it is checked)

// Setting checkbox state
agree.checked = true; // Now checked

// Radio buttons
const plans = form.elements.plan;
console.log(plans.value); // "pro" (value of the checked radio9

// Change selection
plans.value = "enterprise"; // Selects the enterprise radio
// Or individually:
form.elements.plan[0].checked = true; // Select "free"

Textarea

Textarea values work through .value, not .innerHTML or .textContent:

<textarea name="message">Hello World</textarea>
const textarea = form.elements.message;

// Reading
console.log(textarea.value); // "Hello World"

// Setting
textarea.value = "New message content\nWith multiple lines";

// WRONG: don't use innerHTML for textarea values
// textarea.innerHTML = "text"; // Works but can cause XSS issues
caution

Always use .value to read and write textarea content, never .innerHTML. Setting .innerHTML on a textarea can introduce XSS vulnerabilities if the content contains HTML, and it does not update the control's internal state properly in all browsers.

Date and Time Inputs

<input type="date" name="birthday" value="1990-06-15">
<input type="time" name="alarm" value="07:30">
<input type="datetime-local" name="meeting" value="2024-01-15T14:30">
const form = document.forms[0];

// Values are always strings in ISO format
console.log(form.elements.birthday.value); // "1990-06-15"
console.log(form.elements.alarm.value); // "07:30"
console.log(form.elements.meeting.value); // "2024-01-15T14:30"

// Use valueAsDate for Date objects (date inputs only)
console.log(form.elements.birthday.valueAsDate); // Date object

// Use valueAsNumber for timestamps
console.log(form.elements.birthday.valueAsNumber); // 645408000000 (milliseconds)

File Inputs

File inputs are read-only for security reasons. You cannot set their value programmatically:

<input type="file" name="avatar">
<input type="file" name="documents" multiple>
const form = document.forms[0];

// Read selected file(s)
const avatarInput = form.elements.avatar;
console.log(avatarInput.files); // FileList (may be empty)
console.log(avatarInput.files[0]); // File object or undefined
console.log(avatarInput.files.length); // 0 if no file selected

// For multiple files
const docsInput = form.elements.documents;
for (const file of docsInput.files) {
console.log(file.name, file.size, file.type);
}

// You can only clear a file input, not set it
avatarInput.value = ""; // Clears the selection
// avatarInput.value = "file.jpg"; // Does nothing: security restriction

select.options, selectedIndex, Multiple Selection

The <select> element has a richer API than other form controls because it manages a list of options.

Basic Select

<select name="country">
<option value="">Choose a country</option>
<option value="us">United States</option>
<option value="uk" selected>United Kingdom</option>
<option value="fr">France</option>
<option value="de">Germany</option>
</select>
const select = form.elements.country;

// Current value (the selected option's value attribute)
console.log(select.value); // "uk"

// Change selection by value
select.value = "fr";
console.log(select.value); // "fr"

// Selected index (0-based)
console.log(select.selectedIndex); // 3 (France is at index 3)

// Change selection by index
select.selectedIndex = 1; // Selects "United States"
console.log(select.value); // "us"

// Deselect all (for non-required selects)
select.selectedIndex = -1;
console.log(select.value); // ""

The options Collection

select.options returns a live HTMLOptionsCollection of all <option> elements:

const select = form.elements.country;

console.log(select.options.length); // 5

// Iterate over options
for (const option of select.options) {
console.log(`${option.value}: ${option.text} (selected: ${option.selected})`);
}
// "": Choose a country (selected: false)
// "us": United States (selected: false)
// "uk": United Kingdom (selected: true)
// "fr": France (selected: false)
// "de": Germany (selected: false)

Option Properties

Each <option> element has these key properties:

const option = select.options[2]; // United Kingdom

console.log(option.value); // "uk" (the value attribute)
console.log(option.text); // "United Kingdom" (the visible text)
console.log(option.selected); // true (whether this option is selected)
console.log(option.index); // 2 (position in the options collection)
console.log(option.disabled); // false
console.log(option.label); // "United Kingdom" (label attribute or text)

Creating and Modifying Options

const select = form.elements.country;

// Creating options: using the Option constructor
const newOption = new Option("Japan", "jp");
// new Option(text, value, defaultSelected, selected)
const selectedOption = new Option("Canada", "ca", false, true);

// Adding options
select.add(newOption); // Add to the end
select.add(selectedOption, 1); // Insert at index 1

// Alternative: appendChild
const another = document.createElement("option");
another.value = "au";
another.textContent = "Australia";
select.appendChild(another);

// Removing options
select.remove(0); // Remove option at index 0
// or
select.options[0].remove(); // Remove specific option

// Replacing an option
select.options[2] = new Option("New Text", "new-value");

Multiple Selection

When a <select> has the multiple attribute, the user can select multiple options:

<select name="languages" multiple size="5">
<option value="js" selected>JavaScript</option>
<option value="py">Python</option>
<option value="rb">Ruby</option>
<option value="go" selected>Go</option>
<option value="rs">Rust</option>
</select>
const select = form.elements.languages;

// select.value returns only the FIRST selected option's value
console.log(select.value); // "js" (only the first one!)

// To get ALL selected values, iterate through options
const selected = [];
for (const option of select.options) {
if (option.selected) {
selected.push(option.value);
}
}
console.log(selected); // ["js", "go"]

// Or use selectedOptions (modern approach)
const selectedValues = Array.from(select.selectedOptions).map(opt => opt.value);
console.log(selectedValues); // ["js", "go"]

// Selecting/deselecting programmatically
select.options[1].selected = true; // Also select Python
select.options[0].selected = false; // Deselect JavaScript

console.log(Array.from(select.selectedOptions).map(o => o.value));
// ["py", "go"]
tip

For multiple selects, always use select.selectedOptions or iterate through select.options to get all selected values. The .value property only returns the first selected value, which is misleading.

Populating a Select from Data

function populateSelect(selectElement, items, valueKey, textKey) {
// Clear existing options
selectElement.length = 0;

// Add placeholder
selectElement.add(new Option("Select an option...", ""));

// Add items
for (const item of items) {
selectElement.add(new Option(item[textKey], item[valueKey]));
}
}

const countries = [
{ code: "us", name: "United States" },
{ code: "uk", name: "United Kingdom" },
{ code: "fr", name: "France" }
];

populateSelect(form.elements.country, countries, "code", "name");

form.submit(): Programmatic Submission

The form.submit() method submits a form programmatically. This is the JavaScript equivalent of the user clicking the submit button.

Basic Usage

const form = document.forms.login;

// Programmatic submission
form.submit();

Critical Difference: submit() Does NOT Trigger the submit Event

This is one of the most important things to know. When you call form.submit() programmatically, the submit event does not fire. Any validation or processing in your submit event handler is bypassed:

const form = document.forms.login;

form.addEventListener("submit", (event) => {
event.preventDefault();
console.log("Submit handler ran"); // Does NOT run with form.submit()
validateAndSend(form);
});

// This bypasses the event handler entirely!
form.submit(); // Form submits without validation, page reloads

If You Need the Event to Fire: Use requestSubmit()

The requestSubmit() method (available in modern browsers) triggers the submit event and runs the browser's built-in constraint validation, just like a user-initiated submission:

const form = document.forms.login;

form.addEventListener("submit", (event) => {
event.preventDefault();
console.log("Submit handler ran"); // This DOES run with requestSubmit()
validateAndSend(form);
});

// This triggers the submit event and validation
form.requestSubmit();

// You can also pass a specific submit button
const submitBtn = form.querySelector('[type="submit"]');
form.requestSubmit(submitBtn);

Comparison

Featureform.submit()form.requestSubmit()
Fires submit eventNoYes
Runs HTML validationNoYes
preventDefault() worksN/A (no event)Yes
Submit button's formaction usedNoYes (if button passed)
Browser supportAll browsersModern browsers

Programmatic Submission with Fetch

The most common modern pattern is preventing the default submission and using fetch:

const form = document.forms.contact;

form.addEventListener("submit", async (event) => {
event.preventDefault();

const submitBtn = form.querySelector('[type="submit"]');
submitBtn.disabled = true;
submitBtn.textContent = "Sending...";

try {
const response = await fetch(form.action, {
method: form.method,
body: new FormData(form)
});

if (response.ok) {
showMessage("Form submitted successfully!");
form.reset();
} else {
showMessage("Submission failed. Please try again.");
}
} catch (error) {
showMessage("Network error. Please check your connection.");
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "Submit";
}
});

form.reset(): Resetting to Defaults

The form.reset() method resets all form controls to their initial values (the values specified in the HTML attributes, not empty).

Basic Reset

<form name="settings">
<input name="username" value="alice">
<input name="theme" value="light">
<select name="language">
<option value="en" selected>English</option>
<option value="fr">French</option>
</select>
<input type="checkbox" name="notifications" checked>
</form>
const form = document.forms.settings;

// Change all values
form.elements.username.value = "bob";
form.elements.theme.value = "dark";
form.elements.language.value = "fr";
form.elements.notifications.checked = false;

// Reset to initial HTML values
form.reset();

console.log(form.elements.username.value); // "alice" (back to initial9
console.log(form.elements.theme.value); // "light"
console.log(form.elements.language.value); // "en"
console.log(form.elements.notifications.checked); // true

reset() Triggers the reset Event

Unlike submit(), reset() does trigger the reset event, which can be prevented:

form.addEventListener("reset", (event) => {
const confirmed = confirm("Reset all fields to defaults?");
if (!confirmed) {
event.preventDefault(); // Cancel the reset
}
});

Reset vs. Clear

reset() restores initial values, it does not clear everything:

// If the HTML is: <input name="field" value="initial">
form.elements.field.value = "changed";
form.reset();
console.log(form.elements.field.value); // "initial" (NOT empty)

// To truly clear all fields:
function clearForm(form) {
for (const element of form.elements) {
switch (element.type) {
case "text":
case "email":
case "password":
case "textarea":
case "tel":
case "url":
case "number":
case "search":
element.value = "";
break;
case "checkbox":
case "radio":
element.checked = false;
break;
case "select-one":
case "select-multiple":
element.selectedIndex = 0;
break;
case "file":
element.value = "";
break;
}
}
}

new FormData(form): Collecting All Form Data

The FormData API is the modern way to collect all form data at once. It creates a key-value representation of the form that can be sent directly with fetch, iterated over, or manipulated.

Basic Usage

<form name="registration">
<input name="username" value="alice">
<input name="email" value="alice@example.com">
<input name="age" value="30">
<select name="plan">
<option value="free">Free</option>
<option value="pro" selected>Pro</option>
</select>
<textarea name="bio">Hello world</textarea>
<input type="checkbox" name="newsletter" checked>
<input type="file" name="avatar">
</form>
const form = document.forms.registration;
const formData = new FormData(form);

// FormData automatically collects all named form controls
// with their current values

Reading Values

const formData = new FormData(form);

// Get a single value
console.log(formData.get("username")); // "alice"
console.log(formData.get("email")); // "alice@example.com"
console.log(formData.get("plan")); // "pro"
console.log(formData.get("bio")); // "Hello world"

// Check if a key exists
console.log(formData.has("username")); // true
console.log(formData.has("missing")); // false

// Get all values for a key (useful for multi-select, same-name checkboxes)
console.log(formData.getAll("features")); // ["wifi", "parking"]

Iterating Over FormData

const formData = new FormData(form);

// Iterate all entries
for (const [key, value] of formData) {
console.log(`${key}: ${value}`);
}
// username: alice
// email: alice@example.com
// age: 30
// plan: pro
// bio: Hello world
// newsletter: on

// Using entries(), keys(), values()
for (const key of formData.keys()) {
console.log(key);
}

for (const value of formData.values()) {
console.log(value);
}

Converting to Plain Object

const formData = new FormData(form);

// Convert to a plain object
const data = Object.fromEntries(formData);
console.log(data);
// { username: "alice", email: "alice@example.com", age: "30", plan: "pro", ... }

// Note: Object.fromEntries only keeps the LAST value for duplicate keys
// For multiple values with the same name, handle manually:
const dataWithArrays = {};
for (const [key, value] of formData) {
if (dataWithArrays[key] !== undefined) {
if (!Array.isArray(dataWithArrays[key])) {
dataWithArrays[key] = [dataWithArrays[key]];
}
dataWithArrays[key].push(value);
} else {
dataWithArrays[key] = value;
}
}

Modifying FormData

You can add, change, or remove entries before sending:

const formData = new FormData(form);

// Add a new field
formData.append("timestamp", Date.now().toString());
formData.append("source", "web");

// append adds a value (can create duplicates)
formData.append("tag", "important");
formData.append("tag", "urgent");
console.log(formData.getAll("tag")); // ["important", "urgent"]

// set replaces the value (no duplicates)
formData.set("username", "bob"); // Replaces "alice"
console.log(formData.get("username")); // "bob"

// Delete a field
formData.delete("bio");
console.log(formData.has("bio")); // false

Sending FormData with Fetch

FormData integrates seamlessly with fetch. When used as the body, the browser automatically sets the correct Content-Type header (multipart/form-data):

form.addEventListener("submit", async (event) => {
event.preventDefault();

const formData = new FormData(form);

// Add extra data not in the form
formData.append("submittedAt", new Date().toISOString());

try {
const response = await fetch("/api/register", {
method: "POST",
body: formData // Content-Type is set automatically
});

const result = await response.json();
console.log("Registration result:", result);
} catch (error) {
console.error("Submission failed:", error);
}
});
caution

Do not set the Content-Type header manually when sending FormData. The browser needs to set it automatically to include the correct multipart boundary string:

// WRONG: breaks file uploads
fetch("/api/upload", {
method: "POST",
headers: { "Content-Type": "multipart/form-data" }, // DON'T DO THIS
body: formData
});

// CORRECT: let the browser set Content-Type
fetch("/api/upload", {
method: "POST",
body: formData // Browser adds: Content-Type: multipart/form-data; boundary=...
});

Sending as JSON Instead

If your API expects JSON, convert the FormData first:

form.addEventListener("submit", async (event) => {
event.preventDefault();

const formData = new FormData(form);
const jsonData = Object.fromEntries(formData);

const response = await fetch("/api/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(jsonData)
});
});

FormData Without a Form

You can create a FormData object without a form and build it manually:

const formData = new FormData(); // No form argument

formData.append("username", "alice");
formData.append("email", "alice@example.com");

// Add a file programmatically
const blob = new Blob(["Hello, World!"], { type: "text/plain" });
formData.append("file", blob, "hello.txt");

const response = await fetch("/api/upload", {
method: "POST",
body: formData
});

Checkbox and Radio Behavior in FormData

FormData only includes checkboxes that are checked. Unchecked checkboxes are completely omitted:

<input type="checkbox" name="agree" value="yes">              <!-- unchecked -->
<input type="checkbox" name="newsletter" value="yes" checked> <!-- checked -->
const formData = new FormData(form);
console.log(formData.has("agree")); // false: unchecked, not included
console.log(formData.get("newsletter")); // "yes": checked, included

This matches how browsers natively submit forms. If you need to detect unchecked checkboxes, check the form element directly:

const agreeChecked = form.elements.agree.checked; // false

Disabled Elements Are Excluded

FormData does not include disabled form controls, matching native browser submission behavior:

<input name="active" value="yes">
<input name="disabled-field" value="secret" disabled>
const formData = new FormData(form);
console.log(formData.has("active")); // true
console.log(formData.has("disabled-field")); // false: disabled elements are excluded

Summary

Forms in JavaScript have a rich, dedicated API that goes beyond generic DOM manipulation. Understanding these form-specific properties and methods lets you build dynamic, responsive forms with clean, maintainable code.

ConceptKey Point
document.formsLive collection of all forms. Access by index, name, or id.
form.elementsLive collection of all form controls. Access by index or name.
form.elementNameShorthand for form.elements.elementName. Can conflict with native form properties.
element.formBack-reference from any form control to its parent form.
input.valueString value for text inputs. Always a string, even for type="number".
input.checkedBoolean for checkboxes and radio buttons.
input.valueAsNumberNumeric value for number and date inputs.
textarea.valueUse .value, never .innerHTML, to read/write textarea content.
select.valueValue of the selected option. For multiple selects, use selectedOptions.
select.optionsLive collection of <option> elements.
select.selectedOptionsCollection of currently selected options (for multiple selects).
new Option(text, value)Constructor for creating option elements.
form.submit()Submits the form. Does NOT trigger the submit event or run validation.
form.requestSubmit()Submits the form. DOES trigger the submit event and runs validation.
form.reset()Resets all controls to initial HTML values. Triggers the reset event.
new FormData(form)Collects all form data as key-value pairs. Works directly with fetch.
FormData and checkboxesOnly checked checkboxes are included. Unchecked ones are omitted entirely.
FormData and disabledDisabled elements are excluded from FormData.
FormData with fetchDo not set Content-Type manually. The browser sets it with the correct boundary.

Key rules to remember:

  • Use form.elements.name instead of form.name to avoid conflicts with native form properties
  • All .value properties return strings. Use .valueAsNumber or parse explicitly for numeric values.
  • form.submit() bypasses validation and event handlers. Use form.requestSubmit() when you need them.
  • form.reset() restores initial HTML values, not empty values
  • FormData excludes unchecked checkboxes and disabled elements
  • Never set Content-Type when sending FormData with fetch
  • For multiple selects, iterate select.selectedOptions instead of relying on select.value