📝 Why Forms Matter
Everything else on a web page — text, images, videos — is the website talking to you. Forms are how you talk back. Every login, every search, every checkout, every comment — they all start with a form.
But forms are more than just boxes on a screen. A well-built form is accessible, uses the right input types, and validates data before it's sent — all without a single line of JavaScript.
🎯 By the end of this module, you will:
- Build forms with
<form>,action, andmethod - Pair every input with a proper
<label> - Choose the right input type for each field
- Use checkboxes, radio buttons, and dropdowns
- Group related fields with
<fieldset>and<legend> - Add built-in validation without JavaScript
📦 The <form> Element
The <form> element is a container that wraps all your inputs, labels, and buttons. It tells the browser: "This is a group of fields — when submitted, send the data somewhere."
<form action="/submit" method="POST"> <label for="name">Name:</label> <input type="text" id="name" name="name"> <button type="submit">Send</button> </form>
Key Attributes
action="/login". If omitted, the form submits to the current page.
GET or POST.
GET vs POST
Data is appended to the URL: /search?q=cats. Visible, bookmarkable, limited size. Use for searches and filters — anything the user might want to share or bookmark.
Data is sent in the request body — not visible in the URL. No size limit. Use for logins, registrations, and any form that changes data on the server.
How a Form Submits Data
&password=secret123
method, the default is GET. If you omit action, the form submits to the current page. The name attribute on each input is what the server receives — without it, that field's data is not sent.
🏷️ Labels & Inputs
The <label> + <input> pair is the fundamental building block of every form. A label tells the user (and screen readers) what a field is for. An input collects the data.
Two Ways to Connect Them
<label for="email">Email:</label> <input type="email" id="email" name="email">
The for attribute on the label matches the id on the input. This is the clearest and most reliable connection.
<label> Email: <input type="email" name="email"> </label>
The input is inside the label — no for/id needed. Works well for compact layouts like checkboxes.
Why Labels Matter
placeholder is grey hint text inside an input — it disappears when the user starts typing. It cannot replace a <label>. Users with cognitive disabilities, users who tab through forms, and screen readers all need a real, visible label. Placeholder is a hint, not a label.
🎨 Input Types Gallery
HTML5 gives you specialized input types that automatically show the right keyboard on mobile, provide built-in validation, and offer native UI like date pickers and color selectors. Always pick the most specific type.
Text Types
type="text"
General-purpose text. The default — use it when no specialized type fits.
type="email"
Validates email format automatically. Shows the @ keyboard on mobile devices.
type="password"
Hides the typed characters with dots. Browsers may offer to save/autofill passwords.
type="search"
Like text, but browsers may show a clear button (✕) and style it as a search field.
type="tel"
Shows a phone keypad on mobile. No built-in validation — phone formats vary worldwide.
type="url"
Validates URL format. Shows a keyboard with / and .com on mobile.
Specialized Types
type="number"
Numeric input with spinner arrows. Use min, max, and step to constrain values.
type="date"
Shows a native date picker. No need for a JavaScript date library.
type="time"
Time selector with hours and minutes. The browser provides a native time picker UI.
type="range"
A slider control. Perfect for volume, brightness, or any value within a range. Pair it with <output> to show the value.
type="color"
Opens a native color picker. Returns a hex value like #ff6600.
type="file"
File upload control. Use accept to filter types: accept="image/*" or accept=".pdf,.doc".
🧭 Which Input Type Should I Use?
Yes → 2–5 options: radio. 5+ options: <select>. Multiple allowed: checkbox. No → continue ↓
Yes → type="checkbox" (single, standalone). No → continue ↓
Exact value needed → type="number". Approximate range → type="range". No → continue ↓
Yes → type="date" or type="time". No → continue ↓
Yes → type="file". Use accept to filter types. No → continue ↓
Yes → type="password". The browser masks the characters. No → continue ↓
Email → type="email". Phone → type="tel". URL → type="url". Search → type="search". Color → type="color". No → continue ↓
Short text → type="text". Long text → <textarea>. These are your universal fallbacks.
type="email" shows the @ key, type="tel" shows the number pad, type="url" shows the .com key. This tiny HTML change dramatically improves the mobile experience.
☑️ Selection Controls
Beyond text inputs, forms need ways to choose from options. HTML gives you four selection patterns — each designed for a different scenario.
🔘 Pick ONE
The user must choose exactly one option from a group. Options are mutually exclusive.
☑️ Pick MANY
The user can select zero or more options independently. Each choice is a separate toggle.
name form a mutually exclusive group. If two radios have different names — they're independent, and the user can select both. This is one of the most common form bugs.
📄 Textareas & Other Fields
Not everything fits in a single-line input. Here are three more form elements you should know.
🗂️ Form Structure
Large forms need organization. HTML gives you <fieldset> and <legend> to group related fields, and <button> to trigger actions.
Form Anatomy
<form>
<fieldset>
<legend>
Personal Info
<label> + <input>
First Name: ▬▬▬
<label> + <input>
Email: ▬▬▬▬▬▬
<fieldset>
<legend>
Account
<label> + <input>
Password: ●●●●●●
<button>
Create Account
Forms nest elements logically: <form> wraps everything, <fieldset> groups related fields, <legend> names each group, and <label> + <input> pairs collect the data.
The Three Button Types
type="submit"
Sends the form data. This is the default type — a <button> inside a form submits it unless you specify otherwise.
type="reset"
Clears all fields back to their default values. Rarely used — users almost never want to erase everything they typed.
type="button"
Does nothing by default. Use it for JavaScript-powered actions (like "Add another item") that shouldn't submit or reset the form.
<button> can contain any HTML — text, icons, images. An <input type="submit"> can only display plain text via its value attribute. Always prefer <button> for flexibility and semantic clarity.
Putting It All Together
<form action="/register" method="POST"> <fieldset> <legend>Personal Info</legend> <label for="fname">First Name:</label> <input type="text" id="fname" name="fname" required> <label for="lname">Last Name:</label> <input type="text" id="lname" name="lname" required> </fieldset> <fieldset> <legend>Account</legend> <label for="reg-email">Email:</label> <input type="email" id="reg-email" name="email" required> <label for="reg-pass">Password:</label> <input type="password" id="reg-pass" name="password" required minlength="8"> </fieldset> <button type="submit">Create Account</button> </form>
This form uses <fieldset> to group related fields, proper <label> pairing, specific input types, validation attributes, and a clear submit button. It's accessible, semantic, and functional.
✅ Built-in Validation
HTML can validate form data before it's submitted — no JavaScript needed. The browser checks the constraints you set and shows error messages in the user's language.
Validation Attributes
required
Field must be filled — empty submission is blocked.
required
minlength / maxlength
Minimum and maximum text length.
minlength="3" maxlength="50"
min / max
Minimum and maximum value for numbers and dates.
min="18" max="120"
pattern
A regex the value must match.
pattern="[A-Z]{3}-\d{4}"
step
Allowed number increments.
step="0.01"
type="email" automatically checks for an @ symbol. type="url" checks for a valid URL format. type="number" rejects non-numeric input. You get validation for free just by choosing the right type.
Try It — Submit with Invalid Data
♿ Accessible Forms
Forms are one of the hardest things for screen reader users to navigate. A few simple rules make the difference between a usable form and an impossible one.
The Form Accessibility Rules
aria-describedby for help text. If a field has instructions ("Password must be 8+ characters"), connect them: <input aria-describedby="pass-help"> + <span id="pass-help">...</span>. The screen reader reads both the label and the help text.
autocomplete attribute. Values like autocomplete="email", autocomplete="given-name", and autocomplete="new-password" help browsers auto-fill forms — saving time and reducing errors for all users.
"Edit text. Edit text. Edit text. Button."
(No idea what each field is for. The user must guess.)
"Personal Info group. First name, required, edit text. Last name, required, edit text. Account group. Email, required, edit text. Create Account, button."
(Every field is announced with its label and context!)
✏️ Practice: Building Better Forms
Look at each piece of HTML below. Identify the problems, then reveal the correct version.
Exercise 1: Fix the Form
This "form" uses only divs and has no accessibility. Rewrite it mentally with proper form elements, then reveal the answer.
<div class="form"> <div class="row"> <div>Name</div> <input type="text"> </div> <div class="row"> <div>Email</div> <input type="text"> </div> <div class="row"> <input type="text" placeholder="Your message..."> </div> <div class="btn" onclick="send()">Send</div> </div>
<form action="/contact" method="POST"> <label for="c-name">Name:</label> <input type="text" id="c-name" name="name" required> <label for="c-email">Email:</label> <input type="email" id="c-email" name="email" required> <label for="c-msg">Message:</label> <textarea id="c-msg" name="message" required></textarea> <button type="submit">Send</button> </form>
Exercise 2: Spot the Errors
This form uses proper tags but has five accessibility and structural mistakes. Find them all.
<form> <input type="text" placeholder="Your name"> <!-- ?? --> <label>Email</label> <!-- ?? --> <input type="email" id="user-email"> <input type="radio" name="plan" value="free"> Free <!-- ?? --> <input type="radio" name="pricing" value="pro"> Pro <input type="text"> <!-- ?? --> <input type="submit" value="Sign Up"> <!-- ?? --> </form>
❌ No <label> for name input — placeholder is not a label. It disappears when the user types.
❌ <label>Email</label> has no for attribute — it's not connected to the email input. Should be <label for="user-email">.
❌ Radio buttons have different name values (plan vs pricing) — they won't form a group. The user can select both.
❌ Last <input type="text"> has no label, no name, no purpose — completely inaccessible and its data won't be submitted.
❌ "Free" and "Pro" text is not wrapped in <label> — clicking the text won't select the radio button. Use <label><input type="radio"> Free</label>.
Exercise 3: Choose the Right Input
For each scenario, which input type or element should you use?
1. The user's date of birth.
→<input type="date"> — native date picker, no JS needed
2. A star rating from 1 to 5.
→<input type="range" min="1" max="5"> — slider with constrained values
3. The user's email address.
→<input type="email"> — validates @ format, shows @ keyboard on mobile
4. A long comment or feedback message.
→<textarea> — multi-line text input, not <input type="text">
5. Choosing a country from a list of 195 options.
→<select> — dropdown for large predefined lists (or <datalist> if the user should also type freely)
📋 Module Summary
Wraps inputs, sends data with action (where) and method (GET for reads, POST for writes). The name attribute is what the server receives.
Use <label for="id"> to connect labels to inputs. Makes them clickable, accessible, and standards-compliant. Placeholder ≠ label.
email, number, date, range, color — each type shows the right keyboard, provides validation, and offers native UI for free.
required, min/max, minlength/maxlength, pattern — HTML validates without JS. But always validate on the server too.
<fieldset> + <legend> group related fields. <button> is preferred over <input type="submit"> — it can contain any HTML.
Every input needs a label. Group with fieldsets. Use aria-describedby for help text. Use autocomplete for faster filling.