Skip to content

Forms and inputs

docs.scrimba.com

Almost every website that does something for you, a search box, a login, a checkout, a comment field, is built on a form. A form is how a page stops talking at the visitor and starts listening: it collects what someone types, ticks, or selects, and hands that data off to be acted on. This chapter is about the HTML that makes that possible.

The form element

A form is a container that groups together the things a visitor fills in. You wrap your inputs and a button inside a <form> element, and the form's job is to gather everything up and send it off when the visitor is done.

html
<form>
  <label>Your name</label>
  <input>
  <button>Send</button>
</form>

Think of it like a paper form you fill in at a doctor's office. The sheet holds all the boxes, you write in each one, and at the bottom you hand the whole sheet back at once. The <form> element is that sheet.

A <form> is the container that collects its controls and submits them as one bundle. Two attributes decide what happens on submit: action is the address the data is sent to, and method is how it is sent.

html
<form action="/subscribe" method="post">
  <label for="email">Email address</label>
  <input id="email" name="email" type="email">
  <button type="submit">Subscribe</button>
</form>

When the visitor triggers a submit, the browser collects every named control inside the form, packages the values, and sends a request to the action address. You do not wire any of that up yourself. The <form> element gives you this behaviour for free, which is why grouping controls in a real form beats scattering loose inputs on the page.

A <form> is a native submission mechanism: the built-in browser behaviour that gathers the form's controls into a request and sends it, with no scripting required. On submit the browser reads the action (the URL the request goes to) and method (get or post, covered later in this chapter), serialises the controls into the request, and navigates. That default is the reason a form still works with JavaScript disabled or not yet loaded, which is the baseline you build up from rather than the thing you replace.

html
<form action="/subscribe" method="post">
  <label for="email">Email address</label>
  <input id="email" name="email" type="email">
  <button type="submit">Subscribe</button>
</form>

Two behaviours catch people out. First, a form submits on Enter when focus is in a single-line text field, not only on a button click, so keyboard submission is built in and worth testing. Second, the default submit reloads or navigates the page; when you handle a form with JavaScript you call event.preventDefault() to stop that navigation and take over. Treat the native submit as the floor: it should do something sensible even before any script runs. Progressive enhancement, layering script on top of working HTML, is the reason to start here.

JunoThe form element A <form> is the container that holds your inputs and a button, and it sends everything off together when someone submits. Picture the paper form at a desk: one sheet, lots of boxes, handed back all at once. Wrap your fields in a form and you get that gather-and-send behaviour without doing anything clever.
JunoThe form element The <form> bundles its controls and submits them; action says where to, method says how. You get the collecting and sending for free, so reach for a real form instead of loose inputs plus your own click handler. It saves you code and works before any script loads.
JunoThe form element Native submission gathers the controls and sends a request to action with no script involved, which is exactly why a form still works when JavaScript is off. Remember it submits on Enter in a text field, not just on a click. When you do take over with JavaScript, event.preventDefault() stops the default navigation, and the plain form underneath stays your safety net.

Inputs and labels

An <input> is the box a visitor types into. On its own though, an input is an empty box with no clue what it is for. That is what a label is for: it is the bit of text that tells the visitor what to put in the box.

html
<label for="city">City</label>
<input id="city">

Every input needs a label. You connect the two by giving the input an id and pointing the label at it with for, using the same word in both. Once they are linked, clicking the label puts the cursor in the box, which is a small kindness that makes the form easier to use for everyone.

An <input> collects a single value; a <label> names it. You associate them by matching the label's for attribute to the input's id:

html
<label for="username">Username</label>
<input id="username" name="username" type="text" required>

A few attributes earn their place on most inputs. name is the key the value is sent under (covered in the naming section). required blocks submission until the field is filled. placeholder shows faint hint text inside the box, but a placeholder is not a label: it vanishes the moment someone types, so it cannot replace the real thing. Every input still needs its own <label>.

There are two ways to associate a <label> with its control, and the distinction matters for accessibility. The first is explicit association: the label carries a for attribute whose value equals the control's id. The second is implicit association: you wrap the control inside the label element, and no for or id is needed.

html
<!-- explicit: for matches id -->
<label for="phone">Phone</label>
<input id="phone" name="phone" type="tel">

<!-- implicit: the input sits inside the label -->
<label>
  Phone
  <input name="phone" type="tel">
</label>

Both give the control an accessible name, the text a screen reader announces when the field gains focus, so a visitor who cannot see the layout still knows what to type. Prefer explicit association: it survives CSS layouts that move the label away from the input, and it works when styling forces the two apart. A field with no associated label is announced as a bare edit box with no purpose, which is one of the most common accessibility failures on the web. The accessibility chapter goes wider on this.

JunoInputs and labels The <input> is the box people type in, and the <label> is the text telling them what goes there. Link them with matching for and id and clicking the label jumps straight to the box. Give every single input a label; a lonely box with no words next to it only confuses people.
JunoInputs and labels Match the label's for to the input's id and the two are linked. Lean on required to block empty submits, but do not let a placeholder stand in for a label: it disappears the second someone types. Real label, every field, no exceptions.
JunoInputs and labels Two ways to link a label: for matching id, or wrap the input inside the label. Both hand the field an accessible name, the text a screen reader reads out, and explicit for/id is the sturdier choice when CSS pulls things apart. A field with no label reads as a nameless box, which is one of the most common accessibility misses out there.

Other controls

Not everything is a plain text box. Some questions are better answered by ticking, choosing, or picking from a list. A tick box (a checkbox) is an on-or-off switch, and a dropdown lets someone pick one option from several.

html
<label>
  <input type="checkbox"> Send me the newsletter
</label>

<label for="size">Size</label>
<select id="size">
  <option>Small</option>
  <option>Medium</option>
  <option>Large</option>
</select>

You do not have to memorise these. The point for now is that a form can ask its questions in whatever way fits: type it, tick it, or choose it from a list.

The <input> element changes shape based on its type attribute, and the right type gives you a better keyboard, built-in validation, and a native picker for free:

html
<input type="email">     <!-- validates the @ shape, email keyboard on mobile -->
<input type="number">    <!-- numeric keypad, up/down steppers -->
<input type="password">  <!-- masks the characters -->
<input type="date">      <!-- native date picker -->
<input type="checkbox">  <!-- a single on/off toggle -->
<input type="radio">     <!-- pick one from a group -->

Beyond <input>, three elements cover the rest. <textarea> is a multi-line text box for longer writing. <select> with <option> children is a dropdown list. And when you need related fields visually and semantically grouped, wrap them in a <fieldset> with a <legend> that names the group:

html
<fieldset>
  <legend>Delivery speed</legend>
  <label><input type="radio" name="speed" value="standard"> Standard</label>
  <label><input type="radio" name="speed" value="express"> Express</label>
</fieldset>

<label for="message">Message</label>
<textarea id="message" name="message" rows="4"></textarea>

Buttons take a type too. type="submit" sends the form (the default for a button inside a form), type="reset" clears every field back to its starting value, and type="button" does nothing on its own and is there for JavaScript to hook onto.

Each control type carries behaviour worth knowing before you ship it. Radio buttons are grouped by a shared name: only one radio in a group can be selected at a time, and that grouping is what makes them mutually exclusive, so a missing or mismatched name quietly breaks the group into independent toggles.

html
<fieldset>
  <legend>Delivery speed</legend>
  <label><input type="radio" name="speed" value="standard" checked> Standard</label>
  <label><input type="radio" name="speed" value="express"> Express</label>
</fieldset>

A <fieldset> and <legend> are not only visual. The <legend> is announced by screen readers as context for every control in the set, so a radio inside the "Delivery speed" fieldset reads as "Delivery speed, Standard", which is the difference between a comprehensible form and a list of unmoored options. Two more production notes. A checkbox only sends its value when it is ticked; an unticked box is absent from the submission entirely, so the server sees nothing rather than a false, which shapes how you read the data on the other side. And type="submit" is the default type for a <button> inside a form, so a bare <button> you meant as a JavaScript trigger will submit the form and reload the page unless you set type="button". That default catches people constantly.

JunoOther controls A form can ask in more than one way: a tick box for yes-or-no, a dropdown for picking one thing from a list. A checkbox is <input type="checkbox">, and a dropdown is a <select> holding <option> choices. No need to memorise them; the form has more than plain boxes when a question needs it.
JunoOther controls The input's type reshapes it: email, number, date, password, checkbox, radio, each with a fitting keyboard or picker. Reach for <textarea> for long text, <select> for a dropdown, and a <fieldset> with a <legend> to group related fields. Watch the button types: submit is the default inside a form.
JunoOther controls Radios group by a shared name, and a <legend> gives every control in the fieldset spoken context, so it is structure, not decoration. Two traps: an unticked checkbox sends nothing at all, not a false, and a bare <button> defaults to type="submit", so it will reload the page unless you set type="button".

How form data is named and submitted

When a form is sent, each answer needs a label so whoever receives it knows which box it came from. That label is the input's name. You set it once, and it travels with the value.

html
<label for="city">City</label>
<input id="city" name="city">

If the visitor types "Lisbon" into that box, the form sends the pair "city is Lisbon". The name is how the receiving side tells one answer apart from another. An input with no name is left out of the submission entirely, so this small attribute is the one that actually makes the data show up.

Form data is sent as a set of name/value pairs: each control's name becomes the key and what the visitor entered becomes the value. Only controls with a name are included, so a name is what turns a control into submitted data.

html
<form action="/search" method="get">
  <label for="q">Search</label>
  <input id="q" name="q" type="search">
  <label for="sort">Sort by</label>
  <select id="sort" name="sort">
    <option value="recent">Most recent</option>
    <option value="top">Top rated</option>
  </select>
  <button type="submit">Search</button>
</form>

The method attribute picks how those pairs travel. method="get" appends them to the URL as a query string (/search?q=boots&sort=recent), which suits searches and filters you might bookmark or share. method="post" puts them in the request body out of sight, which suits anything that changes data or should not sit in a URL, like a password or a payment.

Every submission is a set of name/value pairs, and three details decide how they are encoded and sent.

The method is the first. get serialises the pairs into the URL as a query string, the ?key=value&key=value text after the address. That makes the request repeatable and bookmarkable, and it means the values are visible in the URL, in browser history, and in server logs, so get is for reads (searches, filters) and never for secrets. post carries the pairs in the request body instead, off the URL, which is the choice for anything that changes state or is sensitive.

The enctype is the second: the encoding that says how the body is formatted. The default, application/x-www-form-urlencoded, packs the pairs into one key=value&... string, which is fine for text. To send a chosen file you must switch to enctype="multipart/form-data", the format that can carry file bytes alongside text fields; a file input will not upload correctly without it.

html
<form action="/upload" method="post" enctype="multipart/form-data">
  <label for="avatar">Profile picture</label>
  <input id="avatar" name="avatar" type="file">
  <button type="submit">Upload</button>
</form>

The third is autocomplete, and it is where naming pays back in UX and accessibility. An autocomplete token tells the browser what a field means in standard terms, so it can offer the right saved value: autocomplete="email", autocomplete="name", autocomplete="current-password", autocomplete="street-address". These tokens are a fixed vocabulary, not free text, and getting them right lets a browser or password manager fill a form in one tap, which matters most for people typing on a phone or using assistive technology.

html
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email">

Keeping the built-in checks honest is a separate job, covered in the form validation chapter; this section is about how the data is shaped and sent once it is valid.

JunoHow form data is named and submitted Each input's name is the tag on its answer, so the receiver knows a value came from the city box and not the email one. Set name, and the box's value gets sent as a labelled pair. Forget it, and that field is silently left out, which trips up a lot of people the first time a form sends back nothing.
JunoHow form data is named and submitted Submitted data is name/value pairs, and only controls with a name get included. Pick the method to match the job: get puts values in the URL for searches and filters you might bookmark, post hides them in the body for anything that changes data or holds a secret. Passwords in a URL is the mistake to never make.
JunoHow form data is named and submittedget writes the pairs into the URL, so it is for reads and never for secrets; post carries them in the body. Switch enctype to multipart/form-data or a file input will not upload. And spend the effort on autocomplete tokens like email and current-password: they are a fixed vocabulary, and getting them right lets a phone or a password manager fill the form in one tap.