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: Usetype="email",type="tel",type="number", etc. They provide validation, appropriate mobile keyboards, and autofill support. - Forgetting
nameattributes: Inputs without anameare 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. Correctautocompletevalues 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
enctypefor file uploads: Withoutenctype="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/idor 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
autocompleteattributes for better browser and password manager integration FormDataAPI simplifies JavaScript form handling- Always validate on the server, regardless of client-side validation