HTML Forms

A <form> collects input from a user and sends it somewhere, usually to a server. The action attribute sets where the data goes, and method sets how.

<form action="/submit" method="POST">
  ...
</form>

Form elements

  • <form>: wraps the whole form.
  • <label>: the visible text describing an input. Required for accessibility, not optional.
  • <input>: the most common form control. Its type attribute decides what kind of input it is.
  • <textarea>: a multi-line text input.
  • <select> and <option>: a dropdown list.
  • <button>: a clickable button.

Labels: why they matter

Every input needs a <label>, connected to it with for (on the label) matching id (on the input).

<label for="fname">First name</label>
<input type="text" id="fname" name="fname" />

This connection does two things. It gives screen readers a name to announce for the input, and it makes the label itself clickable, clicking “First name” focuses the input. Neither of those work if the label and input aren’t associated.

Don’t use placeholder text as a substitute for a label. Placeholder text disappears the moment someone starts typing, so if it was the only description of the field, that description is gone right when it’s most needed. It also has notoriously poor colour contrast by default. Use placeholder only for an example of the expected format, alongside a real label, never instead of one.

<!-- Avoid: placeholder is the only label -->
<input type="email" placeholder="Email" />

<!-- Better: real label, placeholder just shows an example -->
<label for="email">Email</label>
<input type="email" id="email" placeholder="you@example.com" />

A complete example

<form action="/submit" method="POST">
  <div class="field">
    <label for="fname">First name</label>
    <input type="text" id="fname" name="fname" />
  </div>

  <div class="field">
    <label for="lname">Last name</label>
    <input type="text" id="lname" name="lname" />
  </div>

  <button type="submit">Submit</button>
</form>
.field {
  margin-bottom: 12px;
}

.field label {
  display: block;
  margin-bottom: 4px;
}

Wrapping each label/input pair in a container and spacing them with CSS margin is more flexible than stacking <br> tags between elements, and it gives you a real hook for styling.

Input types

<input>’s type attribute changes both its behaviour and, on mobile, which keyboard the browser shows.

<input type="text" />
<input type="email" />
<input type="password" />
<input type="number" />
<input type="tel" />
<input type="url" />
<input type="search" />
<input type="date" />
<input type="time" />
<input type="datetime-local" />
<input type="month" />
<input type="week" />
<input type="color" />
<input type="range" />
<input type="file" />
<input type="checkbox" />
<input type="radio" />
<input type="hidden" />

Using the right type isn’t just a nicety. type="email" brings up an email-optimised keyboard on mobile and gets basic format checking for free. type="number" restricts input to numbers. Using type="text" for everything throws that away.

Checkboxes and radio buttons

<label for="newsletter">
  <input type="checkbox" id="newsletter" name="newsletter" />
  Subscribe to the newsletter
</label>

Checkboxes work independently, any number can be checked. Radio buttons are for choosing exactly one option from a group, and every radio button in the group must share the same name so the browser knows they’re mutually exclusive.

<fieldset>
  <legend>Preferred contact method</legend>

  <label for="contact-email">
    <input type="radio" id="contact-email" name="contact" value="email" />
    Email
  </label>

  <label for="contact-phone">
    <input type="radio" id="contact-phone" name="contact" value="phone" />
    Phone
  </label>
</fieldset>

Grouping fields with fieldset and legend

<fieldset> groups related fields together, and <legend> names the group. This matters most for groups of checkboxes or radio buttons, without it, a screen reader announces each option’s own label but never tells the user what the group as a whole is asking.

<fieldset>
  <legend>Which toppings would you like?</legend>

  <label for="topping-cheese">
    <input type="checkbox" id="topping-cheese" name="toppings" value="cheese" />
    Cheese
  </label>

  <label for="topping-olives">
    <input type="checkbox" id="topping-olives" name="toppings" value="olives" />
    Olives
  </label>
</fieldset>

Select dropdown

<label for="country">Country</label>
<select id="country" name="country">
  <option value="">Select a country</option>
  <option value="us">United States</option>
  <option value="ca">Canada</option>
  <option value="uk">United Kingdom</option>
</select>

Textarea

<label for="comment">Comment</label>
<textarea id="comment" name="comment" rows="5"></textarea>

<textarea> doesn’t use a value attribute for its starting content, any text goes between the opening and closing tags.

Buttons in a form

<button type="submit">Submit</button>
<button type="reset">Reset</button>
<button type="button">Add another field</button>
  • type="submit" submits the form. This is the default if you don’t set type, which is easy to trip over inside a form: a plain <button> with no type will submit the form even if you meant it to do something else with JavaScript.
  • type="reset" clears the form back to its default values. Rarely useful, and easy to click by accident.
  • type="button" does nothing on its own, use it for buttons controlled entirely by JavaScript, like “add another field.”

Common mistakes

  • An input with no associated <label>, or a <label> with a for that doesn’t match any id.
  • Using placeholder as the only label.
  • A group of checkboxes or radio buttons with no <fieldset>/<legend>.
  • Leaving a <button> inside a form without a type, so it accidentally submits the form.
  • Using <br> repeatedly to space out fields instead of CSS margin.

FAQ

Should I use a <div> or a <fieldset> to group fields?

Use <fieldset> when the fields are genuinely one question with multiple parts, like a group of radio buttons. For fields that are just visually grouped but independent (first name and last name, for example), a plain <div> with a class for styling is fine.

How do I stop a form from submitting until the fields are valid?

The browser does a lot of this automatically with attributes like required and type="email". See Form Validation for the full set of built-in tools.

Do I need a form library for this?

Not for simple forms. If you’re building this in React, see React Forms for when a form library like React Hook Form is actually worth adding.

  • Form Validation : required, pattern, and built-in validation messages
  • Links : buttons vs links, if you’re deciding which one a control should be