CSE 134B Web Client Languages
  1. Interaction
  2. Forms
  3. Form Fields

Interaction · Forms 02

Form Fields

HTML has more than twenty kinds of form control, and every one of them ends the same way: as a name and a string in a request. This page goes through them by family, says what each is for, what it looks like to the person filling it in, and exactly what it sends. Read it once in order, then keep it as a reference.

The form and its request followed a form from the button press to the server: the submitter, the checks, the entry list, the encoding. This page fills in the middle of that story, the controls themselves. Choosing the right control is the cheapest usability work there is. The right type brings the right keyboard on a phone, a picker instead of a format to remember, and a check the browser runs for free. The wrong one brings spinners on a ZIP code and a calendar nobody can type into. The examples continue the trail report from the Trail Notes site, the form Organizing Forms builds, and show controls in that page's <form-field> pattern.

1. Five families, one rule

The controls fall into five families, by how the person gives a value:

  • Typed fields take text from the keyboard: text, password, email, url, tel, search, number, and <textarea>, plus hidden, which nobody types into but which belongs with them because its value is a plain string too.
  • Chosen fields offer values to pick from: checkboxes, radio buttons, <select>, and the suggestion list <datalist>.
  • Specialized fields bring their own interface: range, color, the date and time family, and file.
  • Buttons submit the form, or do something else: <button> and the button-like inputs.
  • Display elements show a result and send nothing: <output>, <meter> and <progress>.

The rule that binds them is the one the previous page ended on. When the form is submitted, the browser walks its controls in source order and builds the entry list: one name and value pair for each control that has a name, is not disabled, and, for checkboxes and radio buttons, is checked. However rich the control on screen, a calendar, a color wheel, a slider, what reaches the server is that pair, written as text. A date is the string 2026-10-26. A color is #2e7d32. A ticked box is whatever its value says, or the word on. Everything on this page comes back to the question: what does this control put in the entry list?

Five families of controls, one kind of outputFive boxes on the left: typed fields, chosen fields, specialized fields, buttons, and display elements. Arrows from the first four lead to an entry list in the middle holding name and value pairs such as hiker equals Ana, hazards equals tree, trail equals lake-loop, hiked equals 2026-10-26 and action equals send. The entry list leads to the request body on the right. The display elements box is dashed and its line ends at a note: never sent.Typedtext, email, number, textareaChosencheckbox, radio, select, datalistSpecializedrange, color, date and time, fileButtonsonly the one that was pressedDisplayoutput, meter, progressnever sententry listhiker=Anahazards=treetrail=lake-loophiked=2026-10-26action=sendrequest bodyhiker=Ana&hazards=tree&trail=lake-loop&…
However different the controls look, each one that is sent becomes one or more name and value pairs, in source order. Display elements never take part.

What every control shares

A few attributes mean the same thing on every control, and the rest of the page takes them as read:

  • name is the key in the entry list. No name, nothing sent, however carefully the person filled it in. Several controls may share a name, and then each sends its own pair under it.
  • id is for the page: the label's for, aria-describedby, CSS and script find the control by it. Organizing Forms covers labels; every example here has one.
  • value is the starting value for typed fields and the value that will be sent for checkboxes, radio buttons and buttons. For a typed field, the attribute sets the default and the person's typing changes the current value; a reset puts the attribute's value back.
  • disabled removes a control from the entry list and from the tab order. readonly, on the typed fields that support it, stops editing but keeps the control focusable and sent. Neither is a lock: anyone can remove either attribute in DevTools, or send any value they like without the page, which Form Security demonstrates.
  • The constraint attributes, required, min, max, step, minlength, maxlength and pattern, apply to some types and not others. This page notes which apply where; Form Accessibility and Usability covers how to use them well.

2. The input types at a glance

Most controls are one element, <input>, with a type attribute that changes everything about it: its look, its keyboard, its checks and the format of its value. The table lists all of them. The keyboard column describes what touch keyboards on phones generally do; the details vary by platform and are the subject of Form Accessibility and Usability, along with the inputmode attribute that adjusts them. Baseline is the web-platform status of each feature in October 2026: widely available means it has worked in all the major browsers for at least two and a half years; newly available means it works in all of them now; not Baseline means at least one major browser is missing it.

typeForKeyboard on a phoneWhat it sendsBaseline
textAny short single-line text; the default typeStandardThe text, line breaks removedWidely available
passwordSecrets: the characters are masked on screenStandard, no suggestionsThe text, in the clear (HTTPS protects it)Widely available
emailOne email address, or several with multipleAdds @ and .The address, trimmed; a comma-separated list with multipleWidely available
urlAn absolute URLAdds / and .comThe URL, trimmedWidely available
telA phone number (no format check)Phone keypadThe text as typedWidely available
searchSearch termsStandard, with a Search or Go keyThe textWidely available
numberA quantity you could add or countNumbers (varies by platform)A number such as 3 or 2.5, or emptyWidely available
rangeAn approximate value on a scaleNone: a sliderA number; the midpoint if untouchedWidely available
colorA colorNone: a pickerLowercase hex, #rrggbb; #000000 by defaultNot Baseline (Firefox for Android offers preset colors only)
dateA calendar dateNone: a date picker2026-10-26Widely available
timeA time of day, no time zoneNone: a time picker07:30, with seconds if step asks for themWidely available
datetime-localA date and time, no time zoneNone: a picker2026-10-26T07:30Widely available
monthA year and monthA picker where supported2026-10Not Baseline: a text box in desktop Firefox and Safari
weekAn ISO weekA picker where supported2026-W44Not Baseline: a text box in desktop Firefox and Safari
checkboxA yes or no, or any number from a setNoneIts value (or on) when checked; nothing when notWidely available
radioExactly one from a setNoneThe checked button's value; nothing if none is checkedWidely available
fileOne or more filesNone: the file chooser, camera or photo libraryThe file with multipart encoding; only its name otherwiseWidely available; capture works on phones only
hiddenA value the page sends without showingNoneIts valueWidely available
submitA submit button labeled by its valueNoneIts name and value, only if it was the button pressedWidely available
imageA submit button drawn as an imageNonename.x and name.y: where it was clickedWidely available
resetPuts every control back to its defaultNoneNothing; it never submitsWidely available
buttonA button with no default actionNoneNothingWidely available

Three elements that are not inputs complete the set of controls that send data: <textarea> for multi-line text, <select> for a list of options, and <button>, the flexible button. They are covered with their families below.

Unknown types fall back to text

A browser that does not recognize a type value treats the input as type="text". That rule is the reason the newer types could be added at all: a page using type="week" still works in a browser with no week picker, as an ordinary text box. It also covers mistakes. A typo such as type="emial" produces a working text field with no keyboard help and no check, and nothing on the screen says anything went wrong. The obsolete type="datetime", which some old tutorials still show, falls back the same way; the type that shipped is datetime-local.

The fallback is graceful, but it changes what arrives. A text box where a picker was meant accepts anything, so a person may type "Oct 2026" where a month picker would have produced 2026-10. Where a type is not Baseline, the label or hint should say what format to type, and the server, which checks everything anyway, must cope with what comes.

3. Typed fields

Typed fields are the workhorses. They all look like a box, they all send a string, and they differ in three ways that matter a great deal on a phone: which keyboard appears, what the browser checks, and what the browser does to the value before sending it.

text, the default

type="text" is what an <input> is when it has no type, or an unknown one. It takes a single line; if the person pastes text with line breaks, the browser strips them. It supports minlength, maxlength, pattern, placeholder, readonly and required, and nothing about it limits what characters can be typed. Use it for names, titles, codes, and anything that is not one of the specific types below. Use it, too, for things that look like numbers but are not quantities, as number explains.

<form-field> <label for="hiker">Your name</label><br> <input id="hiker" name="hiker" type="text" autocomplete="name"><br> </form-field>

Sends: hiker=Ana+Garc%C3%ADa. The space becomes + and the accented letter becomes its UTF-8 bytes, percent-encoded, as The form and its request described.

password

type="password" draws each character as a dot. That is all it does to the value. It protects the password from someone looking over the person's shoulder; it does nothing to protect it on the way to the server. In the request body the password is plain text, exactly as typed, and only HTTPS keeps it from anyone else on the network. In a GET form it would land in the URL, the history and the server logs, which is why a login form always uses POST. Browsers warn when a password field appears on a page that is not served over HTTPS.

Password fields turn off autocorrect and suggestions, so the keyboard does not learn or offer the password. Password managers find them by type and by the autocomplete tokens current-password and new-password, which tell the manager whether to fill a saved password or offer a new one. Many sites now add a "Show password" toggle, which switches the type to text with a few lines of script; it helps people type long passwords correctly on small keyboards. Form Accessibility and Usability covers the tokens, and Form Security covers passwords more broadly.

Sends: password=correct+horse. Plain text, in the body.

email

type="email" brings a keyboard with @ and . close at hand on most phones, and a check on submit. The check is deliberately loose. The HTML standard defines a valid address as some allowed characters, an @, and a domain-like part, and it accepts ana@trailnotes with no dot at all, because addresses on internal networks need not have one. It does not, and cannot, tell whether the address exists. The browser trims spaces from the start and end, which saves many a pasted address from failing.

With the multiple attribute the field takes a comma-separated list, and each address is checked. The whole list still goes as one value under one name.

Sends: email=ana%40example.com, the @ percent-encoded like any other reserved character.

url

type="url" brings a keyboard with / and often a .com key, and checks that the value is an absolute URL, scheme included. example.com fails; https://example.com passes. That surprises people who never type the scheme, so a hint saying "starting with https://" saves a round of errors. The check is only about the syntax: any absolute URL passes, including a javascript: or data: URL, so a server that will display the link must check the scheme itself before it ever writes the value into a page.

Sends: album=https%3A%2F%2Fexample.com%2Fphotos.

tel

type="tel" brings the phone keypad, with large digits and the symbols phone numbers use. It does not check anything. Phone numbers differ so much between countries, with and without country codes, extensions, spaces, dashes and parentheses, that the standard defines no format. That is deliberate and right: a strict format written by one country locks out everyone else. If a form needs a particular shape it can add pattern, but the kinder choice is to accept what people type and let the server normalize it. Forms for People returns to phone numbers from around the world.

Sends: phone=%2B1+858-555-0100, exactly as typed, the + encoded so it is not read as a space.

type="search" behaves like text, with small differences: some browsers draw it with rounded corners or a clear button, the Escape key may clear it, and on phones the Enter key is labeled "Search" or "Go". Assistive technology announces it as a search field. A search form is a question about something already on the server, so it should use GET: the results then have a URL that can be bookmarked, shared and opened again. Wrap the form in the <search> element, which gives it the search landmark that screen-reader users can jump to; the element has been Baseline widely available since April 2026.

<search> <form action="/trails" method="get"> <label for="q">Search trails</label> <input id="q" name="q" type="search"> <button>Search</button> </form> </search>

Sends, in the URL: /trails?q=lake+loop.

number, and when not to use it

type="number" is for a quantity: something you could add, subtract, or count up and down by one. Group size, nights, a weight. It accepts only a valid floating-point number, supports min, max and step, usually draws small up and down arrows, called spin buttons, and lets the arrow keys change the value by one step.

<label for="group">Group size</label> <input id="group" name="group" type="number" min="1" max="20">

Sends: group=4. It has more pitfalls than any other type:

  • The default step is 1. Without a step attribute, 2.5 fails the check as a step mismatch. For decimals, set step="0.01" or step="any".
  • Bad input looks empty. Chrome does not let letters be typed at all. Firefox and Safari let the person type them, then report the field as having bad input and block the submission. In every browser, a script reading the field's value sees an empty string, not the letters, so it cannot even show the person what went wrong.
  • The value changes by accident. Spin buttons are small targets next to the text, the arrow keys change the number when the person meant to move on, and in some browsers so does the mouse wheel when the pointer passes over a focused field.
  • Some text constraints do not apply. maxlength, minlength and pattern are ignored on number fields; only min, max and step work.
  • The keyboard is not reliably a keypad. On some phones it is a full keyboard switched to its numbers and symbols, on others a keypad without a minus or decimal key.
  • Exponents are numbers. 1e3 is a valid floating-point number, so it passes.

All of that is tolerable for a quantity. It is wrong for the many things that are made of digits but are not quantities: a ZIP code, a card number, a student ID, a verification code, a year. Nobody increments a ZIP code. A spin button that turns 92093 into 92094 sends a different place. A card number has sixteen digits, which is more than JavaScript's numbers hold exactly (integers are exact only up to 253, about nine thousand trillion), so a script reading it with valueAsNumber may get a different number back. And a code with a leading zero, such as 02134, is a string with five characters, not the number 2134. For these, use type="text", and add inputmode="numeric" to ask for a digit keypad. That is the approach the GOV.UK Design System recommends. Form Accessibility and Usability covers inputmode and the keyboards it brings.

<!-- Avoid: spin buttons on a postal code, and no pattern or maxlength allowed --> <input name="zip" type="number"> <label for="zip">ZIP code</label> <input id="zip" name="zip" type="text" inputmode="numeric" autocomplete="postal-code">

textarea

<textarea> is the multi-line text field, for notes, comments and messages. It is its own element, not an input type, and its default value is its content, the text between the tags, not a value attribute. Whitespace in that content is kept, so a stray indent inside the tags becomes part of the default value. One line break directly after the opening tag is ignored, so the content can start on its own line.

  • rows and cols set the starting size in lines and characters. CSS can override both.
  • maxlength stops typing at that many characters and is the right way to say "500 characters at most." Pair it with a hint that says the limit, and, with a little script, a live count in an <output>.
  • wrap is soft by default: lines wrap on screen, but only the line breaks the person typed are sent. wrap="hard", which needs cols, also sends a line break wherever the text wrapped on screen. It is almost never wanted.

Line breaks have a surprise of their own. However the person's system writes them, the browser sends each one as a carriage return and line feed, so two lines arrive as notes=Fallen+tree%0D%0Anear+the+bridge. Servers usually normalize them; a length check on the server may count each break as two characters where the browser's maxlength counted one.

A textarea that grows: field-sizing

A text area with three rows is too big for a one-line note and too small for a long report. For years the fix was a script that measured the text and set the height. The CSS property field-sizing: content does it natively: the field grows and shrinks with what is typed. It reached all the major browsers in June 2026, when Firefox added it, so it is Baseline newly available. Bound it, so an empty field is not a sliver and a long one does not push the button off the screen, and wrap it in @supports so older browsers simply keep the rows size. It works on text inputs and selects too, though a text input that shrinks to fit a short value looks broken more often than it helps.

@layer components { form-field textarea { min-block-size: 3lh; max-block-size: 12lh; @supports (field-sizing: content) { field-sizing: content; } } }

The lh unit is one line height of the element, so the bounds read as "at least three lines, at most twelve" whatever the font size.

hidden

type="hidden" is a control that is never shown and that nobody fills in, so it needs no label. Its value is sent with the form. It carries things the page knows and the server needs back: the id of the record being edited, a step number in a multi-step form, a token the server issued. One special name exists: a hidden input named _charset_ with no value is sent with the name of the character encoding the form used.

<input type="hidden" name="trail-id" value="417">

Sends: trail-id=417. "Hidden" means not drawn, nothing more. The value is in the page source for anyone who looks, and it can be changed in DevTools or replaced entirely in a request made without the page. A price, a discount, a user id or a role must never travel in a hidden field as if the server could trust it back. Form Security changes a hidden price from $29.99 to one cent to make the point.

4. Chosen fields

Chosen fields let the person pick instead of type, which removes typos, saves effort on a phone, and makes the possible answers visible. They are also where the entry list holds the most surprises: controls that send nothing, controls that send a word nobody wrote, and names that repeat.

Checkboxes

A checkbox is either checked or not. A checked box sends its name and its value. An unchecked box sends nothing: not false, not an empty value, no pair at all. The server learns that a box was unchecked only by not finding it.

A checkbox with no value attribute sends the word on. That is enough for a single yes-or-no question such as "Send me trail news," but a real value says more in the logs and survives being grouped later, so give every checkbox one.

Several checkboxes can share a name to answer one question with any number of answers. Each checked box adds its own pair, in source order, so the name repeats:

<fieldset> <legend>Hazards seen</legend> <input type="checkbox" id="hz-tree" name="hazards" value="tree"> <label for="hz-tree">Fallen tree</label><br> <input type="checkbox" id="hz-washout" name="hazards" value="washout"> <label for="hz-washout">Washout</label><br> <input type="checkbox" id="hz-wildlife" name="hazards" value="wildlife"> <label for="hz-wildlife">Wildlife</label> </fieldset>

With the first two ticked, the form sends hazards=tree&hazards=washout. With none ticked, it sends no hazards at all. Server frameworks differ in how they read a repeated name: some return a list, some only the first or last value. Some older PHP code relies on naming the boxes hazards[] to get an array; that is a convention of one language's parser, not of HTML, and nothing in the request changes except the name.

What chosen fields sendOn the left, a checkbox group of three hazards with Fallen tree and Washout ticked and Wildlife not ticked, a checkbox for trail news left unticked, and a select listing Lake Loop under the group label Valley, with Lake Loop chosen. On the right, the request body: hazards equals tree, hazards equals washout, trail equals lake-loop. Wildlife and trail news send nothing, and the group label Valley is not sent.Hazards seen[x] Fallen tree[x] Washout[ ] Wildlife[ ] Send me trail newsTrailValley(•) Lake Loopsenthazards=treehazards=washouttrail=lake-loopnot sent at allunticked boxes (wildlife, news)the optgroup label "Valley"
A checked box sends its own pair, so a shared name repeats. An unchecked box sends nothing, and an optgroup's label is only for the eye.

A few more facts about checkboxes. The checked attribute sets the starting state, and a script reads and sets the current state through the checked property. A required checkbox must be checked, which suits an "agree to the terms" box but not a group: HTML has no native way to say "tick at least one of these," which takes a script. A checkbox can also be shown as indeterminate, a dash instead of a tick, to mean "some of the boxes below are ticked"; that state can only be set by script, is purely visual, and is never sent. And a box ticked before the person arrives is a choice made for them; in consent questions the law in many places forbids it, as Forms for People explains.

Radio buttons

Radio buttons answer a question with exactly one choice. The buttons in a group share a name; that shared name is what makes them a group, so checking one unchecks the others. Each has its own id, so each label finds its own button, and its own value, which is what is sent.

<fieldset> <legend>Trail conditions</legend> <input type="radio" id="cond-dry" name="conditions" value="dry" required> <label for="cond-dry">Dry</label><br> <input type="radio" id="cond-muddy" name="conditions" value="muddy"> <label for="cond-muddy">Muddy</label><br> <input type="radio" id="cond-snow" name="conditions" value="snow"> <label for="cond-snow">Snow or ice</label> </fieldset>

Sends: conditions=muddy. If no button is checked, the group sends nothing, which is why a question that must be answered gets required on one of its buttons. A radio button with no value sends on, which tells the server only that something was chosen, not what: always give radio buttons a value.

The keyboard treats a group as one stop. Tab moves into the group, to the checked button or the first one; the arrow keys move between buttons and check them as they go; Tab again leaves the group. That is one reason a radio group is better than a row of separate buttons for a single choice, and one reason the group needs a <fieldset> and <legend>, covered in Organizing Forms.

Whether to check one button from the start depends on the question. When there is a sensible default that most people want, such as standard shipping, checking it saves a step. When any default would put words in the person's mouth, such as a satisfaction rating or a yes-or-no consent, leave all of them unchecked. Once a group has a checked button, the person cannot uncheck all of them again without a reset, so if "none" is a real answer, add it as a choice.

select, option and optgroup

<select> offers a list of <option> elements and sends the value of the one chosen. Each option's value attribute is what is sent; its text is what is shown. An option with no value sends its text instead, with extra spaces collapsed. A select shows the first option, or the one marked selected, before anyone touches it, and sends that option if nobody does. So a select that must be answered starts with an empty-valued prompt and carries required: the empty value counts as no answer, and the browser asks the person to choose.

<label for="trail">Trail</label> <select id="trail" name="trail" required> <option value="">Choose a trail</option> <optgroup label="Valley"> <option value="lake-loop">Lake Loop</option> <option value="falls">Falls Canyon</option> </optgroup> <optgroup label="Ridge"> <option value="summit">Summit Ridge</option> </optgroup> </select>

Sends: trail=lake-loop. <optgroup> draws a heading over a set of options, and its label is for the eye only: it is never sent. If two groups contain options with the same text, such as "North Lot" under two different parks, put the distinction into the values, valley-north and ridge-north, because the server will not see the group. A disabled option is shown but cannot be chosen; a disabled optgroup disables all its options. An <hr> between options draws a separator line in desktop browsers and in Safari on iOS, though it is not announced to screen readers.

A select is right when there are more choices than radio buttons comfortably show, roughly more than five or six, and the person knows what they are looking for, such as a country or a state. With few choices, radio buttons are better: all the answers are visible at once, and choosing takes one tap instead of two. With very many, such as every city in a country, a long scrolling list is hard work; a text field with suggestions, below, is usually easier.

select multiple

The multiple attribute turns the select into a list box from which several options can be chosen, and each chosen option adds its own pair, so the name repeats just as it does for a checkbox group:

<label for="activities">Activities</label> <select id="activities" name="activities" multiple size="4"> <option value="hike">Hiking</option> <option value="run">Trail running</option> <option value="bike">Mountain biking</option> <option value="horse">Horse riding</option> </select>

Sends: activities=hike&activities=bike, or nothing if none is chosen. The size attribute sets how many rows show. On a desktop, choosing more than one takes Ctrl-click or Command-click, which many people do not know, and a plain click quietly throws away everything chosen before. Phones replace the list with their own picker, which is easier. For a handful of options, a checkbox group sends exactly the same pairs and needs no instructions; prefer it.

Customizable select

For most of the web's history the open list of a <select> was drawn by the operating system and could hardly be styled at all, so design systems replaced it with a pile of <div> elements and script that rarely matched the native control's keyboard and screen-reader behavior. An early attempt at a stylable replacement, a separate <selectmenu> element, was dropped and never shipped. What shipped instead is the same <select>, opted into a new rendering by CSS:

@layer components { @supports (appearance: base-select) { select, ::picker(select) { appearance: base-select; } ::picker(select) { border: 1px solid var(--rule); border-radius: 0.375rem; padding-block: 0.25rem; } option { padding: 0.375rem 0.75rem; &:checked { font-weight: 600; } } } }

appearance: base-select on the select and on its ::picker(select), the pop-up list, switches both to a plain, fully stylable rendering. Options may then contain more than text, such as an icon beside each name; the ::picker-icon pseudo-element styles the arrow, ::checkmark styles the mark beside the chosen option, and a new <selectedcontent> element can mirror the chosen option's content into the closed button. Keyboard behavior, focus, the accessibility role and form submission all stay the select's own, which is the whole point.

Support in October 2026: Chrome and Edge since version 135 (April 2025), Safari since version 27 (September 2026). Firefox has it only behind a setting. That makes it not Baseline. It is, though, a model progressive enhancement: a browser without support ignores the declarations inside @supports and shows the ordinary select it always has, with the same options and the same value. What the control sends does not change: difficulty=moderate either way. Style it where you can, and do not put anything in an option's content that the person needs and that would be lost in a plain list.

datalist: suggestions, not choices

<datalist> attaches a list of suggestions to an ordinary input. The input's list attribute holds the datalist's id; as the person types, the browser offers the options that match.

<label for="trailhead">Trailhead</label> <input id="trailhead" name="trailhead" type="text" list="trailheads"> <datalist id="trailheads"> <option value="North Lot"></option> <option value="Ranger Station"></option> <option value="Lakeside Picnic Area"></option> </datalist>

The control is still a text field, and it sends whatever is in it: trailhead=North+Lot if the person picked a suggestion, trailhead=Nort+Lot if they typed and mistyped it. A datalist never restricts the value. That makes it right for a field with common answers and an open end, such as a city, a job title or a tag, and wrong for a field whose value must be one of a fixed list, which is a <select>'s job.

Datalist has quirks enough that it is not Baseline as a whole. On text-like inputs it works in every major desktop browser, but Firefox for Android shows no suggestion list at all, so the field there is a plain text box. Browsers also differ in what they display: some show an option's label, some its value, some both, so make the value itself readable. On other types support varies more: on range a datalist draws tick marks (in Firefox only since version 110), and on the date, time and color types it works in Chrome and Safari but not in Firefox. Treat it as a convenience that some people will not get, which is all it was meant to be.

5. Specialized fields

Specialized fields replace typing with an interface of their own: a slider, a color picker, a calendar, a file chooser. They can save a great deal of effort, and they hand the server a value in one strict format whatever the person saw. They also have the most uneven support, so each needs a thought about what happens in a browser that falls back to text.

range

type="range" is a slider for a value where the exact number matters less than the position on a scale: an effort rating, a volume, a price limit for browsing. Its min defaults to 0, its max to 100, its step to 1, and if no value is given it starts at the midpoint. It always has a value, so it is always sent, even if the person never touched it: effort=5.

A slider has two weaknesses. It does not show its value, so pair it with an <output> that shows the number, updated by a line of script. And dragging to an exact number is fiddly with a finger or with a tremor; the arrow keys move it one step at a time, which helps keyboard users, but a person who must land on precisely 37 is better served by a number field. Use a range when "about here" is the honest answer.

<label for="effort">Effort, 1 to 10</label> <input id="effort" name="effort" type="range" min="1" max="10" value="5"> <output id="effort-shown" for="effort">5</output>

color

type="color" opens the platform's color picker. Its value is always a color, written as a lowercase seven-character hex string, and it is never empty: a color input with no value starts at black and sends #000000. Sends: marker=%232e7d32, the # percent-encoded. The picker itself differs widely between systems, and Firefox for Android offers only a set of preset colors, which is why the type is not Baseline. Safari also supports newer alpha and colorspace attributes for picking transparency and wide-gamut colors, but no other engine ships them yet, so they are not something a form can rely on.

The date and time family

Five types ask for dates and times, and they share one idea: the person sees a date in the format of their own language and region, and the server receives it in one fixed format, based on ISO 8601. A person in the United States sees 10/26/2026, a person in Britain 26/10/2026, a person in Germany 26.10.2026, and all three forms send 2026-10-26. That removes the oldest ambiguity in forms, whether 03/04 is March or April, without the page knowing anything about the person's locale.

One date, three displays, one valueThree date fields on the left show the same date in three locales: 10/26/2026 for United States English, 26/10/2026 for British English, and 26.10.2026 for German. Arrows from all three lead to a single value on the right: hiked equals 2026-10-26, the format that is sent whatever the display.what the person sees10/26/2026en-US26/10/2026en-GB26.10.2026de-DEwhat is senthiked=2026-10-26
The picker speaks the person's format; the request always carries the same one. The server never has to guess whether 03/04 is March or April.
typeSendsSupport in October 2026
date2026-10-26Baseline widely available
time07:30; 07:30:15 when step is under 60 secondsBaseline widely available
datetime-local2026-10-26T07:30Baseline widely available (since 2024)
month2026-10Chrome, Edge and phones; desktop Firefox and Safari show a text box
week2026-W44Chrome, Edge, Firefox for Android, Safari on iOS (since 18.2); desktop Firefox and Safari show a text box

min and max take values in the same format, so min="2026-01-01" keeps the calendar from offering earlier dates, and step sets the granularity: in days for date, in seconds for time, so step="900" asks for quarter hours.

Several cautions come with the family:

  • No time zone. time and datetime-local send a wall-clock time with no zone. 2026-10-26T07:30 means half past seven somewhere. If the server needs an instant, it must know the person's time zone some other way, from their account, from the place the time refers to, or from a hidden field a script fills in. A trail permit at 07:30 means 07:30 at the trailhead, which is usually what the person meant anyway.
  • month and week fall back to text in desktop Firefox and Safari. There the person sees an empty text box with no hint of the format, and may type "October" or "10/2026". If you use these types, say the format in a hint, and make the server accept or reject what arrives with a clear message. Often two selects, a month and a year, or a plain date serve better.
  • A calendar is not always faster. For a date of birth, scrolling a calendar back forty years is slow; three labeled text fields for day, month and year, or a text field with a clear format hint, can be quicker. The GOV.UK Design System uses three fields for memorable dates for exactly this reason. Use a picker for dates near today, such as bookings and reports.
  • The display is the browser's. The page cannot choose the displayed format; it follows the browser's and operating system's language settings. That is a feature, not a bug.

file

type="file" opens the system's file chooser; on phones it offers the photo library, the camera and the files app. It is the one control whose value is not text, and that has a consequence for the whole form: a file is sent only when the form uses method="post" and enctype="multipart/form-data". With the default urlencoded encoding, the browser sends just the file's name, photos=bridge.jpg, and the file itself never leaves the device. The form and its request shows the multipart body with its boundaries.

<form action="/api/echo" method="post" enctype="multipart/form-data"> <form-field> <label for="photos">Photos of the trail</label><br> <input id="photos" name="photos" type="file" accept="image/*" multiple aria-describedby="photos-hint"><br> <output id="photos-hint" class="hint" for="photos">JPEG, PNG or HEIC, up to 10 MB each.</output><br> </form-field> <button type="submit">Upload</button> </form>
  • accept lists what the field wants, as MIME types (image/png), wildcards (image/*) or extensions (.pdf), separated by commas. It is a hint to the chooser, which filters what it shows. It is not a check: the person can usually switch the filter off, and anyone can send any file without the page. The server must check the type and size of every file it receives.
  • multiple lets the person choose several files. Each file becomes its own part in the multipart body, under the same name, the same repeating pattern as a checkbox group.
  • capture, with the value environment (the rear camera) or user (the front camera), asks a phone to open the camera directly instead of offering a choice. It works in Chrome for Android, Firefox for Android and Safari on iOS, and desktop browsers ignore it, so it is not Baseline. Use it only when a fresh photo is the point, such as photographing a receipt or a damaged sign, because it can take away the option of choosing a picture already on the phone.

A script that reads the field's value sees something like C:\fakepath\bridge.jpg whatever the system, a fixed prefix that hides the real folder structure of the person's computer from the page; the files themselves are in the field's files list. Browsers do not let a page set a file input's value to a path, for the same reason: a page must never be able to pick a file from someone's disk on its own.

6. Buttons

Buttons do things: submit the form, run a script, open a dialog. Only a submit button takes part in the entry list, and only when it is the one that was pressed.

button and its type

<button> is the flexible button element. Its content is its label, so it can hold text, an icon with text, or other phrasing content. Its type attribute decides what it does:

  • type="submit" submits its form. This is the default: a <button> inside a form with no type attribute is a submit button.
  • type="button" does nothing by itself. It is for scripts, and for the declarative commands below.
  • type="reset" resets the form, which is covered, and discouraged, below.

The default catches people constantly. A "Show password" toggle or a "Add another hazard" button written as a bare <button> inside a form submits the form when clicked, often before its script has done anything, and the page reloads with a half-filled form. Write the type on every button, every time, and the question never comes up.

<!-- Avoid: inside a form, this submits the form --> <button>Show password</button> <button type="button" aria-pressed="false">Show password</button> <button type="submit" name="action" value="send">Send report</button>

The submitter sends its name and value

A submit button with a name adds its name and value to the entry list, but only if it is the submitter, the button that started the submission. That lets one form offer several actions and lets the server tell them apart:

<button type="submit" name="action" value="send">Send report</button> <button type="submit" name="action" value="draft" formnovalidate>Save draft</button>

Pressing the first adds action=send; pressing the second adds action=draft and, thanks to formnovalidate, skips the browser's checks. Submit buttons can also override the form's other attributes for their own submission, with formaction, formmethod, formenctype and formtarget, as The form and its request showed.

Pressing Enter in a single-line text field submits the form too, an implicit submission. The browser acts as if the form's first submit button, its default button, had been pressed, so that button's name and value are sent. That is one more reason to put the main action first in the source: a "Save draft" button written before "Send report" would quietly become the Enter key's action.

The input buttons

Before <button> was widely supported, buttons were inputs, and they still work. <input type="submit" value="Send"> is a submit button whose value is both its label and, if it has a name, what it sends, so the label and the value cannot differ. type="button" and type="reset" are the input versions of the other two. They cannot contain anything but their text, which is why <button> is the usual choice now; neither is wrong.

type="image" is a submit button drawn as a picture, with src for the image and alt for its accessible name, which is required. It sends something no other control sends: the coordinates of the click within the image, as two pairs, map.x=34&map.y=12. It was once used for clickable maps that submitted a location. For an ordinary button with an icon, a <button> containing an <img> or an SVG and visible text is clearer and sends only what you give it.

Buttons that act without script

A type="button" used to need JavaScript to do anything. Invoker commands change that for a few common actions. The commandfor attribute names an element by its id and command says what to do to it: show-modal and close for a <dialog>, toggle-popover, show-popover and hide-popover for a popover. They became Baseline newly available in December 2025.

<button type="button" commandfor="conditions-help" command="show-modal"> What counts as muddy? </button> <dialog id="conditions-help"> <p>Muddy means boots sink in, or water stands on the trail.</p> <button type="button" commandfor="conditions-help" command="close">Close</button> </dialog>

Neither button sends anything, and the dialog's content is not part of the form's data. The gallery demo has one.

Why reset is an anti-pattern

type="reset" puts every control in the form back to its default value. It does this immediately, with no confirmation and no undo. Nobody fills in a form intending to throw it away, but people do press the wrong button, especially when "Reset" sits next to "Send" in the same style, and a single misplaced tap erases minutes of work. The rare person who wants to start over can reload the page or change the fields. Usability writers have argued against reset buttons for decades, and the case has not changed: the button helps almost nobody and costs some people everything they typed. Leave it out. If a form truly needs a "Clear" action, for instance a filter panel, make it a secondary button, place it away from the main action, and make it undoable.

<!-- Avoid: one slip of the finger erases the whole form --> <input type="submit" value="Send"> <input type="reset" value="Reset"> <button type="submit" name="action" value="send">Send report</button>

7. Display elements

Three elements live in forms to show something rather than collect it. None of them is ever sent. They are labelable, so a <label for> can name them.

output

<output> holds the result of a calculation or of the person's action: a total, a count of characters left, an estimate. Its for attribute lists the controls the result depends on. It belongs to the form, may have a name, and appears in the form's elements, yet its content is never put in the entry list. A script sets it through its value or textContent.

<label for="group-size">Group size</label> <input id="group-size" name="group" type="number" min="1" max="20"> <label for="fee">Permit fee</label> <output id="fee" name="fee" for="group-size">$0</output>

Because it is not sent, an output is safe to show a price or a total, and because it is not sent, the server must work that total out again from the submitted values. The page's number is a preview; the server's is the charge. This course also uses <output> for every hint and error message, as Organizing Forms explains.

meter

<meter> shows a measurement within a known range: disk space used, a password's strength, how full a campsite is. Its value sits between min and max (0 and 1 by default), and three more attributes let the browser color it: low and high mark the edges of the middle band, and optimum says which end is good. With optimum low, a high value is drawn as a warning.

<label for="sites">Campsites taken</label> <meter id="sites" min="0" max="40" low="20" high="35" optimum="0" value="37">37 of 40</meter>

The text inside is the fallback and a useful reminder: the color is not the message. Say the value in words nearby too, because a colored bar means nothing to a person who cannot see the color, and screen readers announce a meter's value inconsistently.

progress

<progress> shows how far a task has come: an upload, the steps of a long form. It has only value and max. With no value at all it is indeterminate, an animation that says "working" without saying how far. Use <progress> for completion and <meter> for a measurement; a campsite count is not a task that finishes.

<label for="step-progress">Step 2 of 4</label> <progress id="step-progress" max="4" value="2">2 of 4</progress>

For steps in a long form, the words "Step 2 of 4" matter more than the bar, as Organizing Forms says.

8. Choosing the right control

With every control on the table, the choice mostly comes down to a few questions about the answer you need.

  • Is it free text? One line: text, or email, url, tel or search when it is one of those. Several lines: <textarea>.
  • Is it a quantity? number, with min, max and the right step. Is it made of digits but not a quantity? text with inputmode="numeric".
  • Is it a position on a scale, where "about here" is enough? range, with an output showing the number.
  • Is it one answer from a few? Radio buttons. From many? A <select>. From very many, or open-ended with common answers? A text field with a <datalist>, checked on the server.
  • Is it any number of answers from a set? A checkbox group. A <select multiple> sends the same pairs but is harder to use.
  • Is it yes or no? One checkbox, for "agree" or "subscribe"; a pair of radio buttons when "no" must be an explicit answer.
  • Is it a date near today? date. A date long ago that people know by heart? Separate fields, or text with a format hint.
  • Is it a file? file, in a POST form with multipart encoding.
  • Does the page know it, and the server need it back? hidden, and the server trusts it no more than anything else.
  • Is it a result to show? <output>, <meter> or <progress>, none of them sent.

Two habits make every one of these choices better. First, ask for the answer in the form people already know it: a country by name rather than code, a time on a clock rather than in minutes since midnight. Converting is the program's job. Second, check what the control sends before you write the server code: submit the form to an echo endpoint, as the gallery does, and read the request. Most form bugs are visible right there, a missing name, an on where a value was meant, a file name where a file was expected.

9. Summary

ConceptKey points
One ruleEvery control that is sent becomes name and value pairs, as text, in source order. No name, nothing sent. disabled is not sent; readonly is. Unknown types fall back to text.
Typed fieldspassword masks the screen, not the request: HTTPS protects it. email and url check syntax only; tel checks nothing and brings a keypad. search belongs in a GET form inside <search>. hidden is visible to anyone who looks.
numberFor quantities only. Default step 1, bad input reads as empty, no maxlength or pattern, accidental changes. ZIP codes, card numbers and IDs are text with inputmode="numeric".
textareaDefault value is its content. maxlength for limits; wrap="hard" rarely. Line breaks are sent as CR LF. field-sizing: content (Baseline newly available, 2026) grows it, bounded with min-block-size and max-block-size.
Checkboxes and radiosChecked sends value (or on); unchecked sends nothing. A shared name repeats: hazards=tree&hazards=washout. Radios share a name, have unique ids and always a value.
selectSends the chosen option's value, or its text. An empty first option plus required forces a choice. optgroup labels are not sent. multiple repeats the name; checkboxes are easier. Customizable select (appearance: base-select, ::picker(select)): Chrome 135, Safari 27, not Firefox, so not Baseline, but it falls back to a normal select.
datalistSuggestions only: any value, typos included, is sent. Not Baseline (no list in Firefox for Android). Use select when the value must be from the list.
Specializedrange always sends (midpoint by default); show its value. color sends #rrggbb. Dates display in the person's format and send ISO: 2026-10-26, no time zone. month and week are text boxes in desktop Firefox and Safari.
fileThe file goes only with POST and multipart/form-data; otherwise only its name. accept is a hint, not a check. capture opens the camera on phones only.
ButtonsA <button> with no type submits: always write the type. Only the submitter's name and value are sent; Enter uses the first submit button. type="image" sends .x and .y. commandfor and command open dialogs and popovers without script. Leave out reset.
Display<output> for results and messages, <meter> for measurements, <progress> for completion. None is sent; the server recomputes anything that matters.