Forms
By the end of this lesson
Build forms with correct input types, labels and validation attributes.
A form is how a page collects information and sends it somewhere. In the expense tool, it is how a claim gets created: a description, a category, an amount, a date.
Forms are the part of HTML where using the right element pays off most. You met label association in the semantic HTML lesson. Here you will see the rest of what the form element and the right input types hand you for free — and the specific shortcuts that quietly take it away again.
<form action="/expenses" method="post">
<fieldset>
<legend>Expense details</legend>
<div class="field">
<label for="description">Description</label>
<input
type="text"
id="description"
name="description"
maxlength="80"
required
/>
</div>
<div class="field">
<label for="amount">Amount in GBP</label>
<input
type="number"
id="amount"
name="amount"
min="0"
step="0.01"
required
/>
</div>
<div class="field">
<label for="spent-on">Date spent</label>
<input type="date" id="spent-on" name="spentOn" required />
</div>
</fieldset>
<button type="submit">Submit claim</button>
</form>- The form element groups the fields and decides where the data goes. It also gives you submission when the reader presses Enter in a text field, which people expect and notice the absence of.
- Every label carries a for value that matches the id of its input. That association is what makes the label announced with the field, and what makes clicking the label focus the field.
- name is what the value is called when it is sent. id is for the label and for CSS or script. They are often the same word, but they do different jobs.
- required tells the browser not to submit while the field is empty, and marks the field for assistive technology as one that must be filled in.
- step="0.01" allows pence. Without it, a number input only accepts whole numbers, which is a confusing error to debug.
- type="submit" is the default for a button inside a form, but writing it makes the intent obvious to the next reader.
The input type does more than restrict what can be typed. On a phone it changes the keyboard that appears, which is the difference between a form that is pleasant to fill in and one that is a chore:
- type="text"
- The default. A normal keyboard. Use it when the value is free text, such as an expense description.
- type="email"
- Shows a keyboard with the @ symbol and a full stop within reach, and the browser checks the value looks like an address. It is a loose check — it will accept an address that does not exist.
- type="tel"
- Shows a phone keypad. It deliberately does not validate, because phone number formats vary too much to check usefully.
- type="number"
- For real quantities: an amount, a count. Gives a numeric keypad and stepper arrows. Do not use it for reference numbers or phone numbers — the arrows and mouse-wheel behaviour make no sense there, and leading zeros are lost.
- type="date"
- Gives the platform's own date picker, which already handles month lengths and the reader's date format. It saves you writing one and saves the reader guessing whether you wanted day or month first.
- type="search"
- A text field marked as a search box. Mobile keyboards typically show a Search key instead of a return key, and browsers may offer a clear button.
- type="url"
- A keyboard laid out for addresses, plus a check that the value has the shape of a URL.
- inputmode
- Tunes the keyboard without changing the field's meaning. inputmode="decimal" on a text field gives a numeric keypad while leaving you in control of the formatting.
Validation attributes let the browser check the value before anything is sent. They cost one word each:
- required — must have a value
- minlength and maxlength — length limits on text
- min and max — range limits on numbers and dates, so an expense cannot be dated next year
- step — the allowed increment, such as 0.01 for currency
- pattern — a required format, used when the type cannot express it
- autocomplete — lets the browser offer a saved value, which makes long forms much faster to fill
<fieldset>
<legend>Category</legend>
<label><input type="radio" name="category" value="travel" required /> Travel</label>
<label><input type="radio" name="category" value="equipment" /> Equipment</label>
<label><input type="radio" name="category" value="training" /> Training</label>
</fieldset>
<div class="field">
<label for="cost-centre">Cost centre code</label>
<input
type="text"
id="cost-centre"
name="costCentre"
pattern="[A-Z]{3}-[0-9]{4}"
title="Three capital letters, a hyphen, then four digits"
required
/>
<p id="cost-centre-hint">For example: FIN-2041</p>
</div>- The three radio buttons share one name, which is what makes them one choice rather than three separate questions. Only the selected value is sent.
- fieldset groups them and legend is the group's question. Without that, a screen reader user hears three options with no idea what is being asked.
- Here each input sits inside its label, which is the other valid way to associate them and is convenient for short option text. Pick one style and keep to it.
- pattern describes the required format. It is checked only when the field has a value, so pair it with required if the field is mandatory.
- title supplies the text the browser shows when the pattern fails. Without it the message is generic and unhelpful. The visible hint below the field does the same job more reliably, because it is there before the error.
Summary
- The form element supplies submission on Enter and triggers the browser's validation — keep it
- Choose the input type from what the value means; on a phone it decides which keyboard appears
- Every field needs an associated label; a placeholder is a hint, not a name
- fieldset and legend turn a set of radio buttons into one understandable question
- Browser validation gives the reader fast feedback, and the server must still check everything
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Try it yourself
Fix a broken form
You inherit an expense form with these problems: labels are placeholders, the submit control is a styled div, the amount is a plain text field, the date is three separate text boxes, and the three category radio buttons have no grouping.
Rewrite it. For each change, write one sentence on what the reader gains.
Show solution
Real labels survive typing and are announced with the field. A real button is reachable by keyboard, triggers browser validation, and makes Enter submit. type="number" with step gives a numeric keypad and rejects text. type="date" replaces three fields and all the ambiguity about ordering with the platform's own picker. fieldset and legend turn three loose radio buttons into one question.
Notice how much of this is deletion rather than addition. The accessible version is shorter than the div-based one, because the elements already do the work.
<form action="/expenses" method="post">
<div class="field">
<label for="description">Description</label>
<input
type="text"
id="description"
name="description"
placeholder="Client lunch, 4 people"
maxlength="80"
required
/>
</div>
<div class="field">
<label for="amount">Amount in GBP</label>
<input type="number" id="amount" name="amount" min="0" step="0.01" required />
</div>
<div class="field">
<label for="spent-on">Date spent</label>
<input type="date" id="spent-on" name="spentOn" max="2030-12-31" required />
</div>
<fieldset>
<legend>Category</legend>
<label><input type="radio" name="category" value="travel" required /> Travel</label>
<label><input type="radio" name="category" value="equipment" /> Equipment</label>
<label><input type="radio" name="category" value="training" /> Training</label>
</fieldset>
<button type="submit">Submit claim</button>
</form>Think about it
Think about it
An employee id looks like 00417. Why is type="number" the wrong choice, and what would you use instead?
Show solution
An id is a label that happens to be written with digits. It is not a quantity: you never add two of them, and the leading zeros are part of the value. A number input drops those zeros, offers stepper arrows that make no sense, and can change the value when the reader scrolls the page with the cursor over the field.
Use type="text" with inputmode="numeric" for the keypad, and a pattern such as [0-9]{5} for the format. The rule generalises: choose the type from what the value means, not from which characters it contains.
Knowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.