What a form is for
A <form> collects named values and sends them somewhere. action is the URL, method is get (values in the query string — searches, filters) or post (values in the body — anything that changes data).
Only controls with a name are sent. A field with no name is invisible to the server, which is the most common reason a form "does not work".
<form action="/subscribe" method="post">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<button type="submit">Subscribe</button>
</form>
Every control needs a label
A placeholder is not a label: it disappears as soon as someone types, it fails contrast tests, and many screen readers ignore it. Use a real <label>.
<label for="id">— theformatches the input'sid. Works anywhere on the page.<label>Email <input …></label>— wrapping needs no ids at all.
A connected label makes the label itself clickable, which enlarges the hit area of small checkboxes and radios enormously on a phone.
Input types earn their keep
| type | What you get |
|---|---|
text | The default. |
email | Email keyboard on phones, format validation. |
tel | A numeric keypad. No validation — use pattern. |
url | URL keyboard and validation. |
number | Numeric input with min, max, step. |
date / time | The native picker. |
password | Masked characters. |
search | A clear button in most browsers. |
checkbox / radio | Radios with the same name form one group. |
file | accept limits the types, multiple allows several. |
hidden | Sent but not shown. |
Validation without JavaScript
The browser can enforce a surprising amount before anything is sent, and it announces the failure for you.
required— cannot be empty.minlength/maxlength— length of text.min/max/step— range of numbers and dates.pattern="[0-9]{4}"— a regular expression. Always pair it with atitleexplaining the format.
<label for="pin">4-digit PIN</label>
<input type="text" id="pin" name="pin"
inputmode="numeric" pattern="[0-9]{4}"
title="Four digits" required>
Anyone can send a request without your page. Every rule you enforce in the browser has to be enforced again on the server.
Autocomplete
autocomplete tells the browser and the password manager what a field is, so it can be filled in one tap: name, email, tel, street-address, postal-code, cc-number, current-password, new-password, one-time-code. It is one attribute and it measurably improves completion rates.
Select, textarea, button
<label for="finish">Finish</label>
<select id="finish" name="finish">
<option value="">Choose…</option>
<optgroup label="Painted">
<option value="matt">Matt</option>
<option value="satin" selected>Satin</option>
</optgroup>
<option value="veneer">Veneer</option>
</select>
<label for="note">Notes</label>
<textarea id="note" name="note" rows="4"></textarea>
<button type="submit">Send</button>
<button type="button">Just a button</button>
If a button is not meant to submit, you must write type="button". This single omission causes more surprise page reloads than anything else in HTML.
Grouping
<fieldset> groups related controls and <legend> names the group. For a set of radio buttons this is what tells a screen reader user what the choice is about — without it they hear the options with no question.
<fieldset>
<legend>Delivery</legend>
<label><input type="radio" name="delivery" value="standard" checked> Standard</label>
<label><input type="radio" name="delivery" value="express"> Express</label>
</fieldset>