Forms & Inputs
Building forms with labels, input types, select and textarea, and built-in HTML5 validation attributes.
The <form> element
A <form> collects user input and submits it to a server:
<form action="/subscribe" method="POST">
<label for="email">Email address</label>
<input type="email" id="email" name="email" required>
<button type="submit">Subscribe</button>
</form>
action— the URL the form data is sent to.method—GETappends data to the URL as a query string (fine for searches, bad for sensitive data);POSTsends data in the request body (used for anything that creates/changes data, or contains sensitive information).- Every input needs a
nameattribute — it's the key used when the data is submitted (email=user@example.com). Withoutname, the field's value is never sent.
Labels: <label for>
Every input needs an associated <label>. This isn't optional politeness — it's a core accessibility requirement, and it also gives sighted users a bigger, easier click/tap target:
<label for="username">Username</label>
<input type="text" id="username" name="username">
The for attribute must match the input's id exactly. Alternatively, wrap the input inside the label to associate them implicitly:
<label>
Username
<input type="text" name="username">
</label>
A screen reader announces the label text whenever the input receives focus. Without it, a user tabbing through the form hears nothing but "edit text" — no indication of what to type.
Input types
The type attribute on <input> does a lot of work: it changes the on-screen keyboard on mobile, enables built-in browser validation, and sometimes swaps in a completely different UI widget.
<input type="text" name="fullname" placeholder="Jane Doe">
<input type="email" name="email" placeholder="jane@example.com">
<input type="password" name="password">
<input type="number" name="age" min="0" max="120">
<input type="date" name="birthday">
<input type="tel" name="phone">
<input type="url" name="website">
<input type="checkbox" name="subscribe" checked>
<input type="radio" name="plan" value="free">
<input type="radio" name="plan" value="pro">
<input type="range" name="volume" min="0" max="100">
<input type="color" name="theme-color">
<input type="file" name="avatar" accept="image/*">
<input type="search" name="query">
| Type | Use case | Notable behavior |
|---|---|---|
email |
Email addresses | Mobile keyboards show @; browser validates basic email shape on submit |
number |
Numeric input | Spinner arrows; respects min/max/step |
date |
Calendar dates | Native date-picker UI, no JS library needed |
checkbox |
Independent on/off toggles | Multiple can be checked at once |
radio |
Mutually exclusive choice | Inputs sharing the same name form one group — only one can be selected |
range |
A value within a bounded range | Renders as a slider |
tel |
Phone numbers | Mobile numeric keyboard; no automatic format validation (phone formats vary too much globally) |
url |
Web addresses | Browser validates it looks like a URL |
file |
File uploads | accept restricts the file picker (e.g. accept="image/*") |
search |
Search boxes | Some browsers show a built-in "clear" (×) button |
Checkboxes and radios with the same name are how a radio group works — the browser enforces that only one radio in a group can be checked, purely from markup, no JavaScript required.
Validation attributes
HTML5 ships built-in client-side validation — no JavaScript needed for the common cases:
<form>
<label for="username">Username (required, 3-16 characters)</label>
<input type="text" id="username" name="username" required minlength="3" maxlength="16">
<label for="age">Age (18+)</label>
<input type="number" id="age" name="age" min="18" max="120" required>
<label for="zip">ZIP code (5 digits)</label>
<input type="text" id="zip" name="zip" pattern="[0-9]{5}" title="Enter a 5-digit ZIP code">
<button type="submit">Sign up</button>
</form>
| Attribute | Effect |
|---|---|
required |
Field must be filled before the form submits |
minlength / maxlength |
Bounds on text length |
min / max |
Bounds on numeric or date values |
pattern |
A regular expression the value must match |
step |
Increment granularity for number/range/date inputs |
When validation fails, the browser blocks submission and shows a native tooltip pointing at the offending field — automatically, with zero JavaScript. This is called constraint validation, and it's always worth using as the first line of defense, with server-side validation as the real source of truth (client-side validation can always be bypassed).
<select> and <textarea>
<select> renders a native 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>
Add multiple to allow selecting more than one option, or group related options with <optgroup>.
<textarea> is for multi-line free text — note it's a paired tag, not a void element, and its content goes between the tags rather than in a value attribute:
<label for="bio">Bio</label>
<textarea id="bio" name="bio" rows="4" cols="40" placeholder="Tell us about yourself"></textarea>
A complete example
<form action="/register" method="POST">
<div>
<label for="name">Full name</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="plan">Plan</label>
<select id="plan" name="plan">
<option value="free">Free</option>
<option value="pro">Pro</option>
</select>
</div>
<div>
<label for="bio">Bio</label>
<textarea id="bio" name="bio" rows="3"></textarea>
</div>
<div>
<input type="checkbox" id="terms" name="terms" required>
<label for="terms">I agree to the terms of service</label>
</div>
<button type="submit">Create account</button>
</form>
Common mistakes
- Omitting
nameon an input — the field's value simply won't be included when the form submits, even though it looks fine visually. - Using
placeholderas a replacement for<label>— placeholder text disappears the moment the user starts typing, and isn't reliably announced by all screen readers the way a real label is. - Relying only on client-side (
required,pattern) validation and skipping server-side validation — client-side checks are a UX convenience, never a security boundary, since they can be bypassed by anyone sending a raw HTTP request. - Forgetting
type="button"on non-submit buttons inside a<form>— a plain<button>inside a form defaults totype="submit"and will submit the form when clicked, which is a common source of "why did my page reload" bugs.