Build a labelled, accessible form with the right input types and built-in validation.
Why this matters
Forms are how people talk back to a website: sign-ups, searches, checkouts, contact messages, surveys. A badly built form loses customers and locks out keyboard and screen reader users. A well-built one does a lot of work for free: the right keyboard appears on phones, the browser fills in known details, and simple mistakes are caught before anything is sent.
The form element
<form action="/subscribe" method="post">
...controls...
<button type="submit">Subscribe</button>
</form>
actionis the URL the data is sent to. On a static site without a server you will often use a third-party form service, or handle the data with JavaScript later.methodisget(data goes in the URL query, good for searches) orpost(data goes in the request body, right for anything that creates or changes something).- Each control's
nameattribute becomes the key in the submitted data:email=sam%40example.com. A control without anameis not submitted at all.
Labels, always
Every control needs a label, exactly as in the accessibility lesson:
<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email">
The autocomplete attribute tells the browser what the field is (name, email, tel, street-address, new-password and more) so it can offer to fill it in.
Pick the right input type
| Type | Gives you |
|---|---|
text | a plain single-line field |
email | an email keyboard on phones, and format checking |
password | hidden characters |
tel | a phone keypad (no format checking, because phone formats vary) |
number | a numeric field with min, max and step |
url | URL keyboard and format checking |
date | a date picker |
checkbox | an on/off choice |
radio | one choice out of a group |
For longer text, use <textarea>. For a choice from a long list, use <select> with <option> elements.
Groups: fieldset and legend
Radio buttons that belong together share a name, and the group gets a <fieldset> with a <legend> that acts as the group's label:
<fieldset>
<legend>Preferred contact method</legend>
<label><input type="radio" name="contact" value="email" checked> Email</label>
<label><input type="radio" name="contact" value="phone"> Phone</label>
</fieldset>
A screen reader announces "Preferred contact method, group" and then "Email, radio button, 1 of 2". Here each input is wrapped inside its label, which connects them without needing for and id.
Built-in validation
HTML can check many rules before the form is sent, with no JavaScript:
<input type="email" name="email" required>
<input type="number" name="age" min="13" max="120">
<textarea name="message" minlength="20" maxlength="500" required></textarea>
<input type="text" name="postcode" pattern="[0-9]{5}" title="Five digits">
required: the field must not be empty.minlength/maxlength: limits on text length.min/max/step: limits on numbers and dates.pattern: a regular expression the value must match.
If a rule fails, the browser blocks the submission and shows a message next to the first invalid field. CSS can style invalid fields with the :invalid and :user-invalid pseudo-classes.
Important: browser validation helps the honest user. It is not security. Anyone can bypass it, so the server must always check the data again.
Buttons
<button type="submit"> sends the form; <button type="button"> does nothing by default (for JavaScript actions). A <button> inside a form with no type is a submit button, which surprises people, so always write the type. The button text should say what happens: "Create account", not "Submit".
Worked example: a newsletter sign-up
<form action="/subscribe" method="post">
<p>
<label for="nl-email">Email address</label>
<input type="email" id="nl-email" name="email" autocomplete="email" required>
</p>
<p>
<label>
<input type="checkbox" name="weekly" value="yes">
Send me the weekly digest
</label>
</p>
<button type="submit">Subscribe</button>
</form>
Try submitting it empty: the browser stops you and points at the email field. Type sam@ and try again: it explains the format problem. You wrote no JavaScript.
Common mistakes
- Placeholders as labels.
- Forgetting
name, so the value is never sent. - Radio buttons with different names, which lets the user select all of them.
- Using
type="text"for everything, losing phone keyboards and free checks. - Trusting client-side validation as if it were security.
Quick recap
<form>withaction,method; each control needs anameand a label.- Choose input types that match the data; group radios with
<fieldset>and<legend>. required,minlength,min,maxandpatterngive free validation; the server still checks.- Always set the button
typeand write clear button text.
Try it
Open the Do tab and rebuild a club sign-up form that currently relies on placeholders and fake buttons.