< Modern HTML >

< form >

The form element: more capable than you may remember

A native data-collection and submission element. HTML gives you submission handling, built-in client-side validation, constraint styling hooks, the full HTTP method and encoding surface, and structured data extraction, all before any JavaScript runs. Form-related features have been among the most actively developed areas of the HTML specification in recent years, which means there is quite a bit here that predates most current training materials.

Built-in with native HTML

The submission model, in full

A form submission is defined by four attributes on the <form> element, each of which can be overridden by the submit button that triggered the submission. See the button element page for formaction, formmethod, formenctype, and formnovalidate.

The combination of method="GET" and a thoughtful form is how search boxes, filter forms, and pagination are meant to work. The URL becomes the state, which means bookmarking, sharing, and back-button navigation all work without anything added.

<form method="get" action="/search">
  <label>Search <input type="search" name="q"></label>
  <label>Category
    <select name="category">
      <option value="">All</option>
      <option value="books">Books</option>
    </select>
  </label>
  <button>Search</button>
</form>

That form produces a URL like /search?q=quilting&category=books. It is bookmarkable, shareable, and the back button restores the exact query. No router needed.

Intercepting submission without breaking the form

The pattern for progressive-enhancement submission, where the form works without JavaScript and the JavaScript layer makes it faster or smoother, is to listen for the submit event and call event.preventDefault():

form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const data = new FormData(form);
  const response = await fetch(form.action, {
    method: form.method,
    body: data,
  });
  // handle response
});

This is worth contrasting with the more common pattern of gathering values from refs or controlled inputs. The native form element already knows the values. FormData(form) reads them all. A form that works with and without JavaScript is a form that still works when a script fails to load, when the user is on a slow connection and submits before hydration, and when the network blips mid-request.

The form-associated attributes on inputs

Any input, button, select, or textarea can reference a form elsewhere on the page using the form attribute:

<form id="checkout">...</form>

<input type="text" name="coupon" form="checkout">
<button form="checkout">Apply</button>

This means form inputs do not have to be descendants of the form element. A sticky footer submit button that lives at the bottom of the page while the form lives in the main content area is a single attribute, not a portal.

Constraint validation API

Every form and every form control exposes the same validation interface:

The invalid event fires on a field when submission is attempted and the field is invalid. It does not bubble, so listen on the field itself or use capture.

The ValidityState object is how a form library is built. If one is being built in-house, this is the API to wrap, not replace.

Custom error messages, localized

Native validation messages are localized to the user's browser locale. If custom messages are needed, like "Passwords must match", setCustomValidity sets the string that the browser will show in its native bubble using the browser's native styling. Custom validation messages then look native, appear in the right place, and work with assistive technology, without building a tooltip system.

Accessibility: the form as a landmark

A <form> element with an accessible name, through aria-label or aria-labelledby, is exposed as a navigation landmark. Screen reader users can jump between landmarks. For sites with multiple forms on a page, such as a search form, a newsletter signup, and a contact form, naming them turns each into a navigable region.

<form aria-labelledby="search-heading">
  <h2 id="search-heading">Search</h2>
  ...
</form>

Fieldset and legend: grouping

The <fieldset> and <legend> elements group related controls and announce the group name as part of each control's accessible name inside. For a "Shipping address" block with five fields, wrapping in a fieldset means each field is announced as "Street address, edit, within Shipping address" by screen readers. That context would otherwise need to be repeated in every label.

Fieldset also has a disabled attribute that disables every descendant form control in one place:

<fieldset disabled>
  <legend>Shipping address</legend>
  <!-- every input here is disabled -->
</fieldset>

Useful while a section is loading, or conditionally based on other form state.

Composite patterns that use <form> well

Already HTML-available

Patterns that HTML handles natively, which are often rebuilt in JavaScript:


Reference:

WHATWG
the-form-element
form-submission algorithm
caniuse.com
mdn-html_elements_form
MDN Docs
Web/HTML/Element/form
Client-side form validation
FormData
CSS: :user-valid
CSS: :user-invalid