Type Form Elements in TypeScript

Learn how to type HTML forms, form fields, and named element access in TypeScript so you can collect and validate user input safely.

7 min read

Forms group input elements together so you can read and validate user data as a set. TypeScript types form elements with dedicated interfaces: the form itself is HTMLFormElement, text inputs are HTMLInputElement, selects are HTMLSelectElement, and textareas are HTMLTextAreaElement. This section covers how to type forms, access named fields, work with selects and checkboxes, and collect all form data safely.

Getting a typed form element

Use querySelector with a generic to get a form with the correct type. HTMLFormElement gives you access to form-specific methods like reset and requestSubmit, plus the elements collection for accessing child fields.

typescripttypescript
const form = document.querySelector<HTMLFormElement>('#signup');
if (form === null) throw new Error('Form not found');

The HTMLFormElement interface provides these key members for working with forms programmatically:

MemberTypePurpose
.elementsHTMLFormControlsCollectionAll form controls by name and position
.reset()functionResets all fields to defaults
.requestSubmit()functionSubmits with submit event fired
.lengthnumberNumber of form controls

Accessing named form fields

The standard DOM way to reach a form field is through form.elements.namedItem. However, its return type is broad because TypeScript cannot know whether a given name refers to an input, a select, or a radio group.

typescripttypescript
const field = form.elements.namedItem('email');
//    ^? const field: Element | RadioNodeList | null
 
if (field instanceof HTMLInputElement) {
  console.log(field.value);      // OK
  console.log(field.type);       // 'text', 'email', etc.
}

The instanceof check tells TypeScript that field is an HTMLInputElement inside the block. The compiler then allows .value and .type.

For a more direct approach that avoids instanceof, use querySelector scoped to the form element. This gives you the precise type immediately:

typescripttypescript
const emailInput = form.querySelector<HTMLInputElement>('#email');
if (emailInput !== null) {
  console.log(emailInput.value);
}

Working with select elements

A select element has its own type, HTMLSelectElement, with properties that do not exist on a plain input. The most commonly used are value for the selected option, selectedIndex for its position, and options for the full list.

typescripttypescript
const roleSelect = form.querySelector<HTMLSelectElement>('#role');
if (roleSelect === null) throw new Error('Select not found');
 
console.log(roleSelect.value);         // selected value
console.log(roleSelect.selectedIndex); // option index
console.log(roleSelect.options.length); // total options

A single select only needs .value, but a multi-select can have more than one option chosen at once. Use .selectedOptions, which returns a live collection of HTMLOptionElement, and loop through it to collect every selected value:

typescripttypescript
const multiSelect = form.querySelector<HTMLSelectElement>('#tags');
if (multiSelect === null) throw new Error('Select not found');
 
const selectedValues: string[] = [];
for (const option of multiSelect.selectedOptions) {
  selectedValues.push(option.value);
}

Each option in the loop is automatically typed as HTMLOptionElement, giving you .value and .text.

Checkboxes and radio buttons

A checkbox input has the same HTMLInputElement type as a text input, but you use .checked instead of .value to read its state. The .value property on a checkbox is the string sent when the form submits, not whether the box is ticked.

typescripttypescript
const agreeCheckbox = form.querySelector<HTMLInputElement>('#agree');
if (agreeCheckbox === null) throw new Error('Checkbox not found');
 
console.log(agreeCheckbox.checked); // boolean

For a group of radio buttons with the same name, form.elements.namedItem returns RadioNodeList instead of a single element. RadioNodeList has a .value property that gives the value of the selected radio button:

typescripttypescript
const plan = form.elements.namedItem('plan');
//    ^? const plan: Element | RadioNodeList | null
 
if (plan instanceof RadioNodeList) {
  console.log(plan.value); // value of selected radio
}

Collecting all form data with FormData

FormData collects every named field's value as a string. It is useful when you want all fields at once rather than reading each one individually.

typescripttypescript
form.addEventListener('submit', (event) => {
  event.preventDefault();
 
  const data = new FormData(form);
  const email = data.get('email');
  //    ^? const email: FormDataEntryValue | null
 
  if (typeof email === 'string') {
    console.log(email.toUpperCase()); // OK
  }
});

FormData.get returns FormDataEntryValue or null, which is string or File or null. If your form has no file inputs, you know the value is a string, but TypeScript still requires the narrowing because the type definition cannot know your specific form's fields. For a more structured approach, read each field individually with its correct type instead of using FormData.

Common mistakes

Accessing .value on a form element instead of a field is a frequent error. HTMLFormElement does not have .value. You need to select a specific field first and then read its value.

Assuming form.elements bracket access returns an input is also common. The bracket access on HTMLFormControlsCollection returns Element or RadioNodeList, which does not have .value. Always narrow the result with instanceof or use querySelector instead.

Not checking for null on a field that may not exist is the third common mistake. Every selector can return null, and you must guard before accessing properties.

See Type Input Values in TypeScript for a deeper look at input-specific properties. See Type Event Handlers in TypeScript for event type details.

Rune AI

Rune AI

Key Insights

  • HTMLFormElement has .elements, .reset, and .requestSubmit for working with forms.
  • form.elements.namedItem returns Element or RadioNodeList or null, so narrow with instanceof.
  • HTMLSelectElement gives .value, .selectedIndex, and .options for dropdowns.
  • Use FormData to collect all form values as strings.
  • Access specific fields with a scoped querySelector call for the most precise type.
RunePowered by Rune AI

Frequently Asked Questions

How do I access named form fields with the correct type?

Use form.elements.namedItem which returns Element, RadioNodeList, or null. Narrow the result with instanceof HTMLInputElement, or use a scoped querySelector with a generic for a more direct typed access to individual fields.

How do I type a select element in TypeScript?

Select elements are typed as HTMLSelectElement. This type has .value for the selected value, .selectedIndex for the index, and .options for the full list. Individual option elements are HTMLOptionElement.

How do I collect all form data with proper types?

Use new FormData with the form element. Each .get call returns FormDataEntryValue or null, which is string or File. For typed values, read each field individually by name with the appropriate property like .value or .checked.

Conclusion

TypeScript gives you HTMLFormElement, HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, and HTMLButtonElement to work with form fields. Use querySelector with generics for direct typed access. Always narrow form.elements results with instanceof.