Form properties and methods
Forms are where most real web apps meet their users: logins, checkouts, search boxes, settings panels. The DOM gives <form> and its controls a set of properties and events that don’t exist on ordinary elements, and once you know them, wiring up a form stops feeling like guesswork.
This lesson is about the plumbing: how to find a form, how to find the controls inside it, and how to read or change what each control holds. The events that fire while a user interacts come in later chapters — here we build the map.
Navigation: form and elements
Every form on the page shows up in one place: document.forms. It’s a named collection, which means it works two ways at once. You can index it by position like an array, or reach in by the form’s name attribute like an object.
document.forms.order; // the form with name="order"
document.forms[0]; // the first form in the document
[0] · .order → <form name=“order”>
[1] · .search → <form name=“search”>
Once you hold a form, every control inside it is reachable through form.elements — again a named collection, so both the name and the numeric index work.
<form name="order">
<input name="qty" value="3">
<input name="note" value="gift">
</form>
<script>
// get the form
let form = document.forms.order; // <form name="order"> element
// get the element
let elem = form.elements.qty; // <input name="qty"> element
alert(elem.value); // 3
</script>
Sometimes several controls share one name. Radio buttons are the classic case — a group of them describes one choice, so they all carry the same name. Checkboxes often do too.
When a name is shared, form.elements[name] no longer hands back a single element. It gives you a collection of all the controls with that name.
<form>
<input type="radio" name="size" value="s">
<input type="radio" name="size" value="m">
</form>
<script>
let form = document.forms[0];
let sizeElems = form.elements.size;
alert(sizeElems[0]); // [object HTMLInputElement]
</script>
A key property of this navigation: it ignores nesting. It doesn’t matter how deep a control sits — wrapped in <div>s, tucked inside table cells, buried in a fieldset. If it belongs to the form, it appears directly in form.elements.
<form>
<div>
<fieldset>
<input name=“a”>
</fieldset>
</div>
<input name=“b”>
</form>
.a → input
.b → input
Try it live. The form below is named signup, and its controls sit at different nesting depths. Type a control’s name (login, email, or city) or a numeric index and watch form.elements[...] reach straight in, regardless of the wrapping markup.
Backreference: element.form
The link runs both directions. A form knows all of its controls through form.elements, and every control knows its form through element.form. This backreference is set by the browser and always points at the owning form.
<form>
<input>
<form id="form">
<input type="text" name="email">
</form>
<script>
// form -> element
let email = form.email;
// element -> form
alert(email.form); // HTMLFormElement
</script>
That backreference is handy in event handlers. Given the element that fired an event, element.form gets you to sibling controls without hard-coding the form’s id.
Form elements
Now the controls themselves. Different control types expose their state through different properties, and mixing them up is a common source of “why is my value empty” bugs.
input and textarea
Text-like controls store what the user typed in input.value, always a string. Checkboxes and radio buttons instead report their on/off state through input.checked, a boolean.
input.value = "New value";
textarea.value = "New text";
input.checked = true; // for a checkbox or radio button
Assigning to these properties updates what the user sees immediately. Reading them gives you the current live state, not whatever the HTML said at page load.
The demo below reads both kinds of state as you interact. Type in the text field to change .value, and toggle the checkbox to flip .checked — the live readout updates on every keystroke and click.
select and option
A <select> is richer, because it wraps a list of <option> children. It exposes three properties worth knowing:
select.options— the collection of<option>sub-elements.select.value— the value of the currently selected option.select.selectedIndex— the number (position) of the currently selected option.
Those three map onto three ways of setting the selection:
- Find the target
<option>(for example insideselect.options) and set itsoption.selectedtotrue. - If you know the value, assign it to
select.value. - If you know the position, assign it to
select.selectedIndex.
<select id="select">
<option value="ash">Ash</option>
<option value="oak">Oak</option>
<option value="pine">Pine</option>
</select>
<script>
// all three lines do the same thing
select.options[2].selected = true;
select.selectedIndex = 2;
select.value = 'pine';
// please note: options start from zero, so index 2 means the 3rd option.
</script>
Note the zero-based indexing: options[2] and selectedIndex = 2 both point at the third option.
Each button below reaches the same option by a different route. Pick one, watch the <select> jump, and see all three readouts stay in sync — because they are three views of one selection.
Most controls let you pick one thing. A <select> is the exception when it carries the multiple attribute — then the user can hold several options selected at once. It’s not common, but it exists.
With a multi-select, the value-based shortcuts stop being enough (there isn’t a single value). You work per-option: toggle each option.selected to build the selection, and read them back by filtering.
<select id="select" multiple>
<option value="olives" selected>Olives</option>
<option value="basil" selected>Basil</option>
<option value="corn">Corn</option>
</select>
<script>
// get all selected values from multi-select
let selected = Array.from(select.options)
.filter(option => option.selected)
.map(option => option.value);
alert(selected); // olives,basil
</script>
Array.from turns the live options collection into a real array so filter and map are available — the collection itself doesn’t carry those methods.
The full behavior of <select> is defined in the HTML specification.
new Option
The specification gives <option> a dedicated constructor — a compact way to build one without document.createElement:
option = new Option(text, value, defaultSelected, selected);
You never have to use it; document.createElement('option') plus attribute assignments does the same job. But it’s shorter. The four parameters:
text— the text shown inside the option.value— the option’s value.defaultSelected— iftrue, theselectedHTML attribute is added.selected— iftrue, the option starts out selected.
That last pair looks redundant but isn’t. defaultSelected controls the HTML attribute — the thing you’d read with option.getAttribute('selected'), representing the “original” state. selected controls the current on/off state of the option. In everyday code you’ll usually set both to the same value, or leave them off entirely — both default to false.
A plain, unselected option:
let option = new Option("Elm", "elm");
// creates <option value="elm">Elm</option>
The same option, selected from the start:
let option = new Option("Elm", "elm", true, true);
Build your own options below. Enter the text and value, choose whether it starts selected, then append it to the live <select> with new Option(...) — no document.createElement in sight.
Once created, an <option> element carries a few useful properties of its own:
option.selectedWhether the option is currently selected.
option.indexThe option’s position among its siblings inside the
<select>.
option.textThe text content the visitor sees.
References
- Specification: https://html.spec.whatwg.org/multipage/forms.html.
Summary
Form navigation gives you a reliable path from the document down to any single control, and back up again.
document.formsA form is available as
document.forms[name/index].
form.elementsControls are available as
form.elements[name/index], or through the shorterform[name/index]. Theelementsproperty works on<fieldset>too, letting you scope to a section.
element.formEvery control references its owning form through the
formproperty.
Reading values depends on the control. Text inputs, textareas, and selects expose input.value, textarea.value, select.value. Checkboxes and radio buttons instead report their state through input.checked.
For a <select>, you can also reach the choice by position with select.selectedIndex or by walking the select.options collection — the go-to approach when multiple is in play.
That’s the foundation. The next chapter covers focus and blur — events that can fire on any element but earn their keep on form controls.