HTML Form Elements Table
| Element | Role | Field note |
|---|---|---|
<form> | Submission container | No action = submits to the CURRENT url - the accidental GET loop |
<label> | The accessible name | for=id doubles as a tap target - placeholder text is NOT a label |
<textarea> | Multi-line input | rows is only a starting height; CSS resize owns the grip |
<select> | Native dropdown | optgroup groups; native pickers beat custom ones on mobile every time |
<button> | Click target | DEFAULT type=submit inside a form - type=button is the page-not-reloading attribute |
<fieldset>+<legend> | Grouped controls | Radios need these - legend is the group name screen readers announce |
<datalist> | Suggestions for free text | Type freely AND pick from hints - not a select replacement |
<output> | Computed result | for=ids ties it to inputs; implicit live region announces updates |
Form semantics ARE the framework: label association, keyboard navigation, autofill, validation and mobile keyboards all come from using the real elements - rebuild them in divs and you inherit none of it, then spend the savings on ARIA and custom JS to claw back what HTML gave for free.
Bottom line: the classic form bug is the button default - any button element inside a form submits that form, so every non-submit button needs the one-attribute fix type=button. After that: label[for] gives an accessible name and a bigger tap target in one line, and fieldset+legend names radio groups.
The honest part: native widgets win on phones, every time. A select element opens the OS picker with the right zoom, momentum and accessibility; a custom dropdown re-implements all three badly. Custom UI earns its cost only when the interaction genuinely exceeds what the native control offers.
How to use
- Wire every label: <label for=email>Email</label> before <input id=email> - click the word, focus the field; on mobile that doubles the tap target.
- Group radios in fieldset with legend - the legend is what screen readers announce before the options, turning ten anonymous circles into a named question.
- Compute visibly: <output for=qty price></output> updated from input events - the implicit live-region behavior announces results without ARIA wiring.
Frequently asked questions
Why does clicking my in-form button reload the page?
The button element defaults to type=submit: inside a form, clicking it submits to the form's action - and with no action attribute, that is the CURRENT url, producing the classic reload-with-question-mark loop. The fix is explicit intent: type=button for UI-only buttons, type=submit (or the default) for the one button that should submit, and a submit handler with preventDefault when JavaScript owns the flow. This default predates SPA habits and still surprises developers every year - it is the single most common form bug.
Is placeholder text an acceptable substitute for a label?
No - and the failure is visible the moment anyone types. The placeholder vanishes on input, so the field's purpose disappears mid-use; it fails contrast requirements in most designs; autofill fills the field and the hint is gone before the user reads it; and many screen readers still announce placeholders inconsistently. The accessible and usable pattern is a visible label (associated via for/id), with placeholder used at most for a formatting EXAMPLE (like dd/mm/yyyy), never for the field's name.
When is datalist better than select?
Datalist is a text input with suggestions; select is a constrained choice. Use datalist when any value is VALID and the list is a convenience - country names where users might type, currencies, previously-used tags. Use select when the value MUST be one of the options: submission validation, reserved categories, anything feeding an API enum. Datalist also inherits every text-input behavior (paste, autocomplete, free typing), which is exactly what select cannot offer - the two are complements, not styling variants.
What does the output element give me over a plain span?
Semantics and the announcement for free. An output element is the machine-readable statement 'this value is computed from those inputs' - the for attribute names the controlling inputs, and browsers expose an implicit live-region role so screen readers announce updates when the value changes. A span requires you to hand-build both: ARIA roles plus manual aria-live wiring. The gap narrows if you already run an ARIA layer, but for progressive enhancement the output element delivers correct announce behavior with zero JavaScript accessibility code.