4 min read
On this page

Forms & Inputs

Form Anatomy

Forms are how users send data to servers. They are the backbone of every login page, checkout flow, search bar, and contact page on the web.

<form action="/api/signup" method="POST">
  <label for="email">Email</label>
  <input type="email" id="email" name="email" required>

  <label for="password">Password</label>
  <input type="password" id="password" name="password" required minlength="8">

  <button type="submit">Sign Up</button>
</form>

Key attributes on <form>:

Attribute Purpose Values
action URL to submit data to Any URL or path
method HTTP method GET or POST
enctype How form data is encoded See below
novalidate Disable browser validation Boolean attribute

enctype Values

Value When to Use
application/x-www-form-urlencoded Default. Key-value pairs, URL-encoded
multipart/form-data Required for file uploads
text/plain Rarely used. No encoding
<!-- File upload requires multipart/form-data -->
<form action="/upload" method="POST" enctype="multipart/form-data">
  <label for="avatar">Profile photo</label>
  <input type="file" id="avatar" name="avatar" accept="image/*">
  <button type="submit">Upload</button>
</form>

Input Types

HTML5 introduced specialized input types that provide built-in validation, appropriate keyboards on mobile, and native UI controls.

Text Inputs

<!-- Generic text -->
<input type="text" name="username" placeholder="jdoe" autocomplete="username">

<!-- Email: validates format, shows email keyboard on mobile -->
<input type="email" name="email" autocomplete="email">

<!-- Password: masks input, offers password manager integration -->
<input type="password" name="password" autocomplete="new-password">

<!-- URL: validates format -->
<input type="url" name="website" placeholder="https://example.com">

<!-- Phone: shows numeric keyboard on mobile, no format validation -->
<input type="tel" name="phone" autocomplete="tel">

<!-- Search: may show clear button, search keyboard -->
<input type="search" name="q" placeholder="Search...">

Numeric Inputs

<!-- Number with constraints -->
<input type="number" name="quantity" min="1" max="99" step="1" value="1">

<!-- Range slider -->
<input type="range" name="volume" min="0" max="100" value="50">

Date & Time Inputs

<!-- Date picker -->
<input type="date" name="birthday" min="1900-01-01" max="2026-04-18">

<!-- Time picker -->
<input type="time" name="meeting-time" min="09:00" max="17:00">

<!-- Date and time -->
<input type="datetime-local" name="appointment">

<!-- Month -->
<input type="month" name="start-month">

Choice Inputs

<!-- Checkbox (multiple selections) -->
<fieldset>
  <legend>Interests</legend>
  <label>
    <input type="checkbox" name="interests" value="html"> HTML
  </label>
  <label>
    <input type="checkbox" name="interests" value="css"> CSS
  </label>
  <label>
    <input type="checkbox" name="interests" value="js"> JavaScript
  </label>
</fieldset>

<!-- Radio buttons (single selection) -->
<fieldset>
  <legend>Experience Level</legend>
  <label>
    <input type="radio" name="level" value="beginner"> Beginner
  </label>
  <label>
    <input type="radio" name="level" value="intermediate"> Intermediate
  </label>
  <label>
    <input type="radio" name="level" value="advanced"> Advanced
  </label>
</fieldset>

Other Input Types

<!-- File upload -->
<input type="file" name="documents" accept=".pdf,.doc" multiple>

<!-- Color picker -->
<input type="color" name="theme-color" value="#3b82f6">

<!-- Hidden field (not visible, still submitted) -->
<input type="hidden" name="csrf_token" value="abc123">

Select, Textarea & Other Controls

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

<!-- Multi-line text -->
<label for="bio">Bio</label>
<textarea id="bio" name="bio" rows="4" maxlength="500"
          placeholder="Tell us about yourself..."></textarea>

Labels: The Most Important Accessibility Feature

Every input must have a label. There are no exceptions. Labels tell users what the input is for, give screen readers something to announce, and expand the clickable area.

Explicit Association (Preferred)

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

The for attribute matches the input's id. Clicking the label focuses the input.

Implicit Association

<label>
  Email address
  <input type="email" name="email">
</label>

The input is wrapped inside the label. This works but explicit association is more robust across assistive technologies.

Placeholders are not labels. They disappear on input focus, have low contrast, and are not reliably announced by screen readers. Never use a placeholder as a substitute for a label.

Built-In Validation

HTML provides validation without JavaScript. The browser checks constraints on submit and shows native error messages.

<form action="/register" method="POST">
  <!-- Required field -->
  <input type="text" name="name" required>

  <!-- Email format validation -->
  <input type="email" name="email" required>

  <!-- Minimum length -->
  <input type="password" name="password" required minlength="8">

  <!-- Pattern matching -->
  <input type="text" name="zipcode" pattern="[0-9]{5}"
         title="Five digit zip code">

  <!-- Number range -->
  <input type="number" name="age" min="18" max="120">

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

Validation Attributes

Attribute Purpose Works With
required Field must be filled All inputs
minlength Minimum character count text, password, textarea
maxlength Maximum character count text, password, textarea
min / max Minimum/maximum value number, date, range
step Allowed value increments number, date, range
pattern Regex the value must match text, password, tel, url

Custom Validation with JavaScript

When built-in validation is not enough, use setCustomValidity:

const password = document.getElementById('password');
const confirm = document.getElementById('confirm-password');

confirm.addEventListener('input', () => {
  if (confirm.value !== password.value) {
    confirm.setCustomValidity('Passwords do not match');
  } else {
    confirm.setCustomValidity('');
  }
});

Fieldset & Legend

Group related inputs and provide a group label:

<fieldset>
  <legend>Shipping Address</legend>

  <label for="street">Street</label>
  <input type="text" id="street" name="street" autocomplete="street-address">

  <label for="city">City</label>
  <input type="text" id="city" name="city" autocomplete="address-level2">

  <label for="state">State</label>
  <input type="text" id="state" name="state" autocomplete="address-level1">

  <label for="zip">Zip Code</label>
  <input type="text" id="zip" name="zip" autocomplete="postal-code" pattern="[0-9]{5}">
</fieldset>

<fieldset> is especially important for radio buttons and checkboxes. Without it, screen readers announce each option independently with no group context.

Autocomplete

The autocomplete attribute helps browsers and password managers fill in forms correctly. Use values like name, email, tel, street-address, current-password, and new-password. Correct autocomplete values dramatically improve user experience, especially on mobile.

Common Pitfalls

  • Missing labels: Every input needs a label. Placeholders are not labels. This is the most common accessibility failure in forms.
  • Using type="text" for everything: Use type="email", type="tel", type="number", etc. They provide validation, appropriate mobile keyboards, and autofill support.
  • Forgetting name attributes: Inputs without a name are not included in form submission data. The form looks correct but the server receives nothing.
  • Not using autocomplete: Users expect their browser to fill in common fields. Correct autocomplete values make this work reliably.
  • Validation only on the client: HTML and JavaScript validation improve UX, but they can be bypassed. Always validate on the server too.
  • Forgetting enctype for file uploads: Without enctype="multipart/form-data", file inputs send the filename instead of the file contents.
  • Click handlers on non-button elements: Using <div onclick="submit()"> instead of <button type="submit">. Buttons are keyboard accessible and announced correctly by screen readers.

Key Takeaways

  • Forms are HTML's data collection mechanism: <form>, <label>, <input>, <select>, <textarea>, <button>
  • Every input must have a label, associated via for/id or wrapping
  • Use specific input types (email, tel, date, number) for validation, mobile keyboards, and autofill
  • Built-in validation (required, pattern, min/max, minlength) reduces JavaScript complexity
  • Group related inputs with <fieldset> and <legend>
  • Use autocomplete attributes for better browser and password manager integration
  • FormData API simplifies JavaScript form handling
  • Always validate on the server, regardless of client-side validation