Skip to content

Forms and Input Types

beginner15 min read

Every App You've Ever Used Has Forms

Login, signup, search, checkout, settings, comments, file uploads — they're all forms. Forms are the web's primary input mechanism. Get them right, and your app feels polished and professional. Get them wrong, and users rage-quit on mobile, screen reader users can't complete tasks, and your data is full of garbage.

Here's the thing most developers miss: HTML forms are shockingly powerful without any JavaScript. Built-in validation, type-specific keyboards on mobile, autocomplete, form submission — the browser handles all of it. Most of the JavaScript we write for forms is reinventing what HTML already does.

Mental Model

Think of an HTML form like a paper form at a government office. Each field has a label explaining what to fill in, a specific type (date, number, checkbox, signature), required fields are marked with an asterisk, and there's a submit button at the bottom. If you forget a required field, the clerk points it out before processing. HTML forms work the same way — labels, types, required markers, and built-in validation, all before any JavaScript runs.

The Form Element

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

The form element wraps all inputs and handles submission:

  • action — where to send the data (a URL)
  • methodget (data in URL query string) or post (data in request body)
  • When the user clicks submit, the browser collects all named inputs and sends them to the action URL

Without JavaScript, this works. The browser validates, submits, and navigates to the response. In a modern SPA, you often intercept submission with JavaScript, but the HTML structure remains the same.

Quiz
What happens when a user submits a form that has no JavaScript handlers?

Labels: The Most Important Form Element

Every input needs a label. No exceptions. Labels tell users (and screen readers) what each field is for.

<!-- Explicit association: for + id -->
<label for="username">Username</label>
<input type="text" id="username" name="username">

<!-- Implicit association: input inside label -->
<label>
  Username
  <input type="text" name="username">
</label>

The for attribute on the label must match the id on the input. This creates an explicit association. When a user clicks the label, the input gets focused — this dramatically improves usability on mobile where small inputs are hard to tap.

Common Trap

Using placeholder as a replacement for labels is one of the most common accessibility violations on the web. Placeholders disappear when the user starts typing, leaving them with no indication of what the field is for. Users with cognitive disabilities or poor short-term memory are particularly affected. Always use a visible label. Placeholders can supplement labels but never replace them.

Input Types

HTML provides specialized input types that change behavior, keyboard, and validation:

<!-- Text inputs -->
<input type="text" name="name">          <!-- Generic text -->
<input type="email" name="email">        <!-- Email validation + @ keyboard on mobile -->
<input type="password" name="pass">      <!-- Masked input -->
<input type="url" name="website">        <!-- URL validation -->
<input type="tel" name="phone">          <!-- Phone keyboard on mobile -->
<input type="search" name="q">           <!-- Search field (clear button on some browsers) -->

<!-- Numeric inputs -->
<input type="number" name="age" min="0" max="120" step="1">
<input type="range" name="volume" min="0" max="100">

<!-- Date and time -->
<input type="date" name="birthday">      <!-- Date picker -->
<input type="time" name="alarm">         <!-- Time picker -->
<input type="datetime-local" name="meeting">

<!-- Selection inputs -->
<input type="checkbox" name="agree">     <!-- Toggle on/off -->
<input type="radio" name="plan" value="free">  <!-- One of many -->
<input type="color" name="theme">        <!-- Color picker -->
<input type="file" name="avatar" accept="image/*">

<!-- Hidden and special -->
<input type="hidden" name="csrf" value="abc123">

The mobile keyboard difference is huge. type="email" shows an @ key. type="tel" shows a number pad. type="url" shows a .com key. Using the right type makes forms dramatically easier to fill on phones.

Quiz
Why is type='email' better than type='text' for an email field?

Built-In Validation

HTML provides powerful validation without writing any JavaScript:

<form action="/api/register" method="post">
  <!-- Required field -->
  <label for="name">Full name</label>
  <input type="text" id="name" name="name" required>

  <!-- Email with pattern -->
  <label for="email">Work email</label>
  <input
    type="email"
    id="email"
    name="email"
    required
    pattern=".+@company\.com"
    title="Must be a @company.com email"
  >

  <!-- Number with range -->
  <label for="age">Age</label>
  <input type="number" id="age" name="age" min="18" max="99" required>

  <!-- Text with length constraints -->
  <label for="bio">Bio</label>
  <textarea id="bio" name="bio" minlength="10" maxlength="500"></textarea>

  <button type="submit">Register</button>
</form>

Validation attributes:

  • required — field must have a value
  • pattern — value must match a regex
  • min/max — numeric or date range
  • minlength/maxlength — text length range
  • title — shown in the validation tooltip (describes the expected format)

The browser shows native error messages when validation fails. You can style valid/invalid states with CSS:

input:valid {
  border-color: green;
}

input:invalid {
  border-color: red;
}

/* Only show error styling after the user has interacted */
input:not(:placeholder-shown):invalid {
  border-color: red;
}
Quiz
When does browser validation trigger on a form?

Selects, Textareas, and Fieldsets

Select Dropdowns

<label for="country">Country</label>
<select id="country" name="country" required>
  <option value="">Choose a country</option>
  <optgroup label="North America">
    <option value="us">United States</option>
    <option value="ca">Canada</option>
    <option value="mx">Mexico</option>
  </optgroup>
  <optgroup label="Europe">
    <option value="uk">United Kingdom</option>
    <option value="de">Germany</option>
    <option value="fr">France</option>
  </optgroup>
</select>

The first option with an empty value acts as a placeholder. optgroup groups related options visually and semantically.

Textareas

<label for="message">Message</label>
<textarea
  id="message"
  name="message"
  rows="5"
  cols="40"
  placeholder="Write your message here..."
  required
  minlength="20"
></textarea>

Unlike input, textarea is not a void element — it has a closing tag. Content between the tags is the initial value.

Fieldsets and Legends

Group related fields with fieldset and label the group with legend:

<fieldset>
  <legend>Shipping Address</legend>
  <label for="street">Street</label>
  <input type="text" id="street" name="street" required>
  <label for="city">City</label>
  <input type="text" id="city" name="city" required>
  <label for="zip">ZIP Code</label>
  <input type="text" id="zip" name="zip" pattern="[0-9]{5}" required>
</fieldset>

fieldset is especially important for radio button groups — the legend provides the question, and each radio's label provides the answer:

<fieldset>
  <legend>Select your plan</legend>
  <label><input type="radio" name="plan" value="free"> Free</label>
  <label><input type="radio" name="plan" value="pro"> Pro ($29/mo)</label>
  <label><input type="radio" name="plan" value="team"> Team ($99/mo)</label>
</fieldset>
Quiz
What is the purpose of the fieldset and legend elements?

Production Scenario: Accessible Registration Form

<form action="/api/register" method="post" novalidate>
  <h2>Create your account</h2>

  <div>
    <label for="reg-email">Email address</label>
    <input
      type="email"
      id="reg-email"
      name="email"
      required
      autocomplete="email"
      aria-describedby="email-hint"
    >
    <p id="email-hint">We will never share your email.</p>
  </div>

  <div>
    <label for="reg-pass">Password</label>
    <input
      type="password"
      id="reg-pass"
      name="password"
      required
      minlength="8"
      autocomplete="new-password"
      aria-describedby="pass-requirements"
    >
    <p id="pass-requirements">At least 8 characters, one uppercase, one number.</p>
  </div>

  <fieldset>
    <legend>Account type</legend>
    <label>
      <input type="radio" name="type" value="personal" checked>
      Personal
    </label>
    <label>
      <input type="radio" name="type" value="business">
      Business
    </label>
  </fieldset>

  <div>
    <label>
      <input type="checkbox" name="terms" required>
      I agree to the <a href="/terms">Terms of Service</a>
    </label>
  </div>

  <button type="submit">Create Account</button>
</form>

Key details:

  • autocomplete attributes help password managers and browser autofill
  • aria-describedby links each input to its hint text — screen readers read the hint after the label
  • novalidate on the form disables browser validation UI (when using custom JavaScript validation instead)
  • Radio buttons are grouped in a fieldset with legend
  • The checkbox label includes the link text, so it reads naturally: "I agree to the Terms of Service"
Execution Trace
User tabs to email
Screen reader: 'Email address, edit text, We will never share your email'
aria-describedby connects the hint to the input
Mobile keyboard
Email keyboard appears with @ and .com keys
type='email' triggers the specialized keyboard
User tabs to password
Screen reader: 'Password, edit text, protected, At least 8 characters...'
type='password' masks input, aria-describedby reads requirements
User submits
Browser validates all required fields, patterns, and constraints
If validation passes, data is sent to /api/register via POST
What developers doWhat they should do
Using placeholder text instead of visible labels
Placeholders vanish when typing, leaving users with no field identification. They also have insufficient contrast in most browsers and are not reliably read by all screen readers
Always use a visible label element. Placeholders disappear on focus and lack accessibility
Using type='text' for everything
Specific input types trigger specialized mobile keyboards and free built-in validation. type='text' gives neither
Use specific types: email, tel, url, number, date — they provide correct keyboards and built-in validation
Missing name attributes on form inputs
The name attribute determines the key in the submitted data. Without it, the input's value is not included in the form submission
Every submittable input needs a name attribute
Forgetting autocomplete attributes
autocomplete helps browsers and password managers fill forms correctly. It dramatically improves UX and conversion rates — especially on mobile
Add autocomplete='email', autocomplete='new-password', etc.

Challenge: Build an Accessible Contact Form

Create a contact form with: name (required), email (required), subject dropdown (with 3 options), message textarea (required, 20-500 characters), and a file attachment (images only, max 5MB). Make it fully accessible.

Show Answer
<form action="/api/contact" method="post" enctype="multipart/form-data">
  <h2>Contact Us</h2>

  <div>
    <label for="contact-name">Full name</label>
    <input
      type="text"
      id="contact-name"
      name="name"
      required
      autocomplete="name"
    >
  </div>

  <div>
    <label for="contact-email">Email address</label>
    <input
      type="email"
      id="contact-email"
      name="email"
      required
      autocomplete="email"
    >
  </div>

  <div>
    <label for="contact-subject">Subject</label>
    <select id="contact-subject" name="subject" required>
      <option value="">Choose a subject</option>
      <option value="general">General Inquiry</option>
      <option value="support">Technical Support</option>
      <option value="billing">Billing Question</option>
    </select>
  </div>

  <div>
    <label for="contact-message">Message</label>
    <textarea
      id="contact-message"
      name="message"
      rows="6"
      required
      minlength="20"
      maxlength="500"
      aria-describedby="message-hint"
    ></textarea>
    <p id="message-hint">Between 20 and 500 characters.</p>
  </div>

  <div>
    <label for="contact-file">Attachment (optional)</label>
    <input
      type="file"
      id="contact-file"
      name="attachment"
      accept="image/*"
      aria-describedby="file-hint"
    >
    <p id="file-hint">Images only. Maximum 5MB.</p>
  </div>

  <button type="submit">Send Message</button>
</form>

Key points:

  • enctype="multipart/form-data" is required for file uploads
  • Every input has a label with matching for/id
  • aria-describedby connects help text to inputs
  • accept="image/*" filters the file picker to images
  • Select has an empty-value placeholder option for "required" validation
  • autocomplete on name and email for faster filling
Key Rules
  1. 1Every input needs a visible label — placeholders are not labels and disappear when typing
  2. 2Use specific input types (email, tel, number, date) for mobile keyboards and free validation
  3. 3Every submittable input needs a name attribute — without it, the value is not sent
  4. 4Group related fields with fieldset/legend — critical for radio buttons and checkboxes
  5. 5Use aria-describedby to connect hint text and error messages to their inputs