1. What a Form Is
A form is how the user sends data back to a server. Everything else in HTML shows things to the user — a form takes things from them: a login, a search, a signup, a checkout.
<form action="/signup" method="POST">
<input type="email" name="email" />
<input type="password" name="password" />
<button type="submit">Sign up</button>
</form>
The <form> wraps a group of inputs and a submit button. When the user submits, the browser collects the values and sends them to the address in action.
The mental picture to hold: the form is the envelope, the inputs are the things inside it, and submitting is dropping it in the mailbox. Two attributes on <form> control the mailing — action (where) and method (how) — and both are their own topic next.
One thing that surprises beginners: a form works with zero JavaScript. The browser handles collecting the data and sending it natively. You'll add JavaScript later to validate or submit without a page reload — but the plain HTML form already works on its own. Start from that, then enhance.
2. action & method: GET vs POST
Two attributes on <form> decide where the data goes and how:
<form action="/search" method="GET"> <!-- data goes in the URL -->
<form action="/login" method="POST"> <!-- data goes in the request body -->
action— the URL the data is sent to.method— how it's sent. Two choices, and the difference matters:
GET puts the data in the URL as a query string: /search?q=shoes&size=42. Use it for searches and filters — things where you want the URL to be shareable and bookmarkable. You can copy that URL, send it to a friend, and they see the same search.
POST puts the data in the request body, hidden from the URL. Use it for anything that changes something or is private — logins, signups, payments, posting a comment.
The rule that keeps you out of trouble:
Never use GET for passwords or sensitive data. It ends up in the URL — visible in the address bar, saved in browser history, and logged by every server it passes through.
That's not a style preference; it's a real security issue. A login form is always POST.
Reality note: in a modern React or Next app, you often won't use action/method directly — JavaScript intercepts the submit and sends the data via fetch instead. But the GET-vs-POST distinction is the same one you'll make in every API call for the rest of your career, so it's worth understanding here first.
3. The name Attribute
name is the most important attribute on an input — and the easiest to forget. It's the label under which the value gets sent:
<input type="email" name="email" />
<input type="password" name="password" />
When this form submits, the server receives email=...&password=.... The name is the key; the input's value is the value.
The trap that wastes an afternoon: an input with no name is not sent at all. It looks fine, the user can type in it, validation works — but when the form submits, that field silently vanishes from the data. No error. You just get incomplete data on the server and no idea why. If a field isn't showing up on the backend, the missing name is the first thing to check.
For radio buttons, name does double duty — it's what groups them so only one can be selected (covered in the radio topic). Same name = same group.
Rule: every input that should send data needs a name. The only inputs that skip it are ones you're reading purely with JavaScript and never submitting.
4. Text Inputs
The type attribute changes what an input accepts and how phones treat it:
<input type="text" name="username" />
<input type="email" name="email" />
<input type="password" name="password" />
<input type="tel" name="phone" />
<input type="url" name="website" />
<input type="search" name="q" />
They can look similar on desktop, but type earns its keep in two ways:
1. Mobile keyboards change. type="email" gives the user a keyboard with @ visible. type="tel" gives a number pad. type="url" shows .com. Using type="text" for an email field means the user hunts for the @ — a small thing that makes your form feel careless on a phone.
2. Free validation. type="email" makes the browser check for a valid email shape on submit, with no JavaScript. type="url" checks for a URL. (More in the validation topic.)
A trap worth knowing: type="password" only hides the characters visually — dots instead of letters. It does nothing for security by itself. The data is still plain text until you send it over HTTPS. Beginners sometimes think type="password" "encrypts" the field. It doesn't; it just stops someone reading over the shoulder.
Rule: pick the type that matches the data. It costs nothing and you get better mobile keyboards and free validation.
5. Number, Range, Date, Color
More input types, each rendering a purpose-built control:
<input type="number" name="qty" min="1" max="10" step="1" />
<input type="range" name="volume" min="0" max="100" />
<input type="date" name="birthday" />
<input type="time" name="slot" />
<input type="color" name="theme" />
<input type="file" name="avatar" />
number— a numeric field with up/down arrows.min,max,stepconstrain it.range— a slider. Samemin/max/step, but for values where the exact number matters less than the feel (volume, brightness).date/time— native pickers. The browser draws a calendar or clock — you don't build one.color— a native color picker.file— a file chooser. Addmultipleto allow several, andaccept="image/*"to filter.
The value of these: you get a full date picker or color picker with one line of HTML and zero JavaScript. Beginners often reach for a heavy library to build a date picker before realizing the browser has one built in.
Trap: type="number" is wrong for things that look like numbers but aren't quantities — phone numbers, credit cards, PINs, postal codes. Those can have leading zeros, spaces, or +, which type="number" strips or rejects. Use type="tel" or type="text" with a pattern instead. Rule of thumb: if you'd never do math on it, it's not a number.
6. Radio & Checkbox
Two inputs for choosing from options — and the difference is single vs multiple:
Radio buttons — pick exactly one from a group:
<input type="radio" name="plan" value="free" id="free" />
<label for="free">Free</label>
<input type="radio" name="plan" value="pro" id="pro" />
<label for="pro">Pro</label>
Checkboxes — pick any number, independently:
<input type="checkbox" name="topping" value="cheese" id="cheese" />
<label for="cheese">Cheese</label>
<input type="checkbox" name="topping" value="olives" id="olives" />
<label for="olives">Olives</label>
The single most common radio bug: the thing that makes radios mutually exclusive is the shared name. Give two radios different names and the user can select both — they're no longer a group. If your radios aren't behaving like radios, check that every option in the group has the same name.
value is required on both — it's what actually gets sent. A checked radio with no value sends nothing useful.
Decision: "pick one" → radio. "pick zero or more" → checkbox. A single yes/no (accept terms, subscribe) is one checkbox.
Reality note: these are the inputs people most often try to fake with styled <div>s because the native ones are "ugly." Resist it early — the native ones are keyboard-accessible and screen-reader-friendly for free. You style them with CSS; you don't replace them with divs.
7. select & datalist
<select> — a dropdown to pick from a fixed list:
<select name="country">
<option value="">Choose...</option>
<option value="eg">Egypt</option>
<option value="sa">Saudi Arabia</option>
</select>
- Each
<option>has avalue(sent to the server) and visible text (shown to the user) — they can differ. - A first empty
<option>acts as a placeholder. <optgroup label="...">groups options under headings.- Add
multipleto allow selecting several.
<datalist> — a text input with suggestions, where the user can also type their own:
<input list="browsers" name="browser" />
<datalist id="browsers">
<option value="Chrome"></option>
<option value="Firefox"></option>
</datalist>
The decision between them: <select> forces a choice from your list — the user must pick one of your options. <datalist> suggests options but lets the user type anything. Use select when only your values are valid (a country, a plan); use datalist when you're helping but not restricting (a search box with recent terms).
Trap: using <select> for a yes/no or a two-option choice. A dropdown for two items is more clicks than radio buttons — the user has to open it, then pick. Two options → radios. A dropdown earns its place around 4+ options.
8. textarea
<textarea> is a multi-line text field — for comments, messages, descriptions, anything longer than a line:
<textarea name="message" rows="5" cols="30" placeholder="Your message"></textarea>
rows— the visible height in lines.cols— the visible width in characters (usually you control width with CSS instead).
The trap that catches everyone once: <textarea> has a closing tag, and its value goes between the tags — not in a value attribute:
<!-- Wrong — value attribute is ignored -->
<textarea value="Hello"></textarea>
<!-- Right — content goes between the tags -->
<textarea>Hello</textarea>
<input> uses value="...". <textarea> uses its inner content. Mixing them up means your default text silently doesn't appear.
Also worth knowing: any whitespace or newlines you put between the tags become part of the value. So keep <textarea></textarea> tight on one line unless you genuinely want that whitespace in the field.
CSS tip for later: resize: vertical lets users drag it taller but not wider, which keeps your layout from breaking.
9. Labels
Every input needs a <label>. It's not decoration — it's what connects a piece of text to its input:
<!-- Method 1: for + id -->
<label for="email">Email</label>
<input type="email" id="email" name="email" />
<!-- Method 2: wrap the input -->
<label>
Email
<input type="email" name="email" />
</label>
Two ways to bind: for on the label matching the input's id, or wrapping the input inside the label.
Why labels genuinely matter — three concrete wins:
- Clicking the label focuses the input. Bigger click target, easier on mobile. For a checkbox, clicking its label toggles it — much easier to hit than the tiny box.
- Screen readers announce the label when the user reaches the input. Without a label, a blind user hears "edit text" with no idea what to type.
- It's a validation and autofill signal — browsers use labels to autofill the right data.
The trap: using a placeholder instead of a label. The placeholder disappears the moment the user starts typing — so they lose the field's name mid-entry, and screen readers don't reliably read placeholders. A placeholder is a hint ("e.g. name@site.com"), never a replacement for the label. Every input gets a real label.
10. fieldset & legend
<fieldset> groups related inputs, and <legend> gives the group a caption:
<fieldset>
<legend>Shipping address</legend>
<label for="street">Street</label>
<input id="street" name="street" />
<label for="city">City</label>
<input id="city" name="city" />
</fieldset>
The browser draws a border around the group with the legend as its title.
Where this earns its place — radio groups. A set of radio buttons needs a group label, and a plain <label> can't do that (each radio has its own label). <fieldset> + <legend> is how a screen reader announces "Plan: Free, Pro, Enterprise" as one question with three answers, instead of three disconnected options:
<fieldset>
<legend>Choose a plan</legend>
<input type="radio" name="plan" value="free" id="free" />
<label for="free">Free</label>
<input type="radio" name="plan" value="pro" id="pro" />
<label for="pro">Pro</label>
</fieldset>
Rule: any time you have a group of choices that share a question (radios, related checkboxes), wrap them in a <fieldset> with a <legend>. For single standalone inputs, you don't need it.
11. Buttons
<button type="submit">Save</button>
<button type="button">Cancel</button>
<button type="reset">Clear</button>
The type attribute is small and it bites hard:
type="submit"— submits the form. This is the default if you don't specify a type.type="button"— does nothing on its own; for JavaScript-driven actions.type="reset"— clears the form back to defaults (rarely wanted — users hit it by accident and lose their work).
The trap that produces "my page reloads for no reason": a <button> inside a <form> with no type defaults to submit. So if you add a button meant to, say, toggle a password's visibility, and forget type="button", clicking it submits the whole form and reloads the page. Any button inside a form that isn't the submit button needs type="button" explicitly.
<button> vs <input type="submit">: both submit. Prefer <button> — it can contain HTML (an icon plus text), while <input type="submit"> can only hold plain text via its value. <button> is the modern default.
And once more, because it's the theme of forms: <button> is for actions, <a> is for navigation. A "Delete" is a <button>; a "Back to home" is an <a>.
12. Native Validation
The browser can validate inputs before submitting — no JavaScript:
<input type="email" required />
<input type="text" required minlength="3" maxlength="20" />
<input type="number" min="1" max="100" />
<input type="text" pattern="[0-9]{5}" title="Enter a 5-digit code" />
required— can't submit while empty.minlength/maxlength— length limits for text.min/max— value limits for numbers and dates.type="email"/type="url"— shape checking for free.pattern— a regex the value must match. Always add atitleexplaining the rule, since the browser shows it in the error.
When validation fails, the browser blocks submission and shows a message next to the field, automatically.
The critical thing to understand — this is not security:
Native validation is a convenience for honest users, not a defense. Anyone can bypass it — disable JavaScript, edit the HTML in DevTools, or send the request directly without your form at all.
So client-side validation improves the experience (instant feedback, no round-trip), but the server must validate everything again. Never trust that data arriving at your backend passed your HTML rules. This is one of the most important habits in web development, and forms are where you first meet it. You'll re-learn it in every backend course.
novalidate on the <form> turns native validation off — useful when you're handling it all in JavaScript instead.
13. Input UX Attributes
Small attributes that make forms feel professional:
<input type="email" name="email"
placeholder="name@example.com"
autocomplete="email"
autofocus
inputmode="email" />
placeholder— a faint hint inside the field. A hint, not a label (see the labels topic).autocomplete— lets the browser autofill saved data. Values likeemail,name,current-password,new-password,cc-number. This is a real UX win people skip — goodautocompletevalues mean the user taps once instead of typing everything.autofocus— focuses this field on page load. Use on one field at most (the main one), never more.inputmode— hints the mobile keyboard without changing validation (numeric,decimal,tel).readonly— visible, selectable, submitted, but not editable.disabled— greyed out, not editable, and not submitted (its value is dropped).
The readonly vs disabled trap: they look similar but differ in one crucial way — a disabled field's value is not sent with the form, while a readonly field's value is sent. If you need the user to see a value they can't change but you still want it submitted, use readonly, not disabled.
14. Form Accessibility
Forms are where accessibility most often breaks, because they're interactive. The good news: if you followed the earlier topics, you're most of the way there. The checklist:
- Every input has a real
<label>— not just a placeholder. (Topic 9.) - Groups of choices use
<fieldset>+<legend>— so radios and related checkboxes read as one question. (Topic 10.) - Errors are announced, not just colored. "The field turned red" tells a colorblind or blind user nothing. Put the error in text next to the field, and mark the input
aria-invalid="true"so screen readers announce it. - Required fields are marked in text or with
required, not by color alone. - The submit button is a real
<button>— keyboard-operable for free.
<label for="email">Email</label>
<input type="email" id="email" name="email" required aria-describedby="email-error" />
<span id="email-error" role="alert">Please enter a valid email.</span>
aria-describedby links the error message to the input, so a screen reader reads the error when the user lands on the field.
Why this is worth the effort: forms are the point where users give you something — a signup, a sale, a message. An inaccessible form doesn't just inconvenience some users; it loses you the conversion entirely for anyone who can't complete it. This is the most business-relevant accessibility in the whole course.
15. Tables
A table is for tabular data — information with rows and columns that relate to each other. A price list, a schedule, a comparison, a spreadsheet-like grid.
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Coffee</td>
<td>$3</td>
</tr>
<tr>
<td>Tea</td>
<td>$2</td>
</tr>
</tbody>
</table>
<table>— the container.<tr>— a table row.<th>— a header cell (bold and centered by default; it labels a column or row).<td>— a data cell.<thead>/<tbody>/<tfoot>— group the header, body, and footer rows.
The one rule that defines correct table use:
Use a table only for data that is genuinely tabular — rows and columns that mean something. Never use a table for page layout.
Twenty years ago people built entire page layouts with tables (because CSS layout didn't exist yet). You'll still see it in old code and email templates. On the modern web, layout is CSS Grid and Flexbox — using a table for layout is inaccessible (screen readers announce it as a data table and read out phantom "row 3, column 2") and painful to make responsive. Tables are for data. Layout is CSS.
16. Table Structure & Spanning
Beyond the basics, a few attributes make tables correct and accessible:
<caption> — a title for the table, and the first child:
<table>
<caption>Q4 Sales by Region</caption>
...
</table>
scope — tells screen readers whether a <th> heads a column or a row. This is what makes a data table actually navigable for blind users:
<th scope="col">Name</th> <!-- heads a column -->
<th scope="row">Total</th> <!-- heads a row -->
colspan / rowspan — make a cell span multiple columns or rows:
<td colspan="2">Spans two columns</td>
<td rowspan="3">Spans three rows</td>
Why scope and <caption> earn their place: a sighted user reads a table by glancing at the headers and cross-referencing. A blind user can't glance — the screen reader relies on scope to say "Price: $3" instead of just "$3" as it moves through cells. Without it, a table of numbers becomes a meaningless stream. <caption> gives the whole table a name so they know what they're reading before diving in. These two are the difference between a table that's accessible and one that's just visually arranged.
Trap: mismatched cell counts. If one row has 3 cells and another has 4 (and you didn't use colspan), the table renders crooked and confuses assistive tech. Every row should account for every column.
17. details & summary
<details> is a native expand/collapse — an accordion or "show more" with zero JavaScript:
<details>
<summary>What's your refund policy?</summary>
<p>Full refund within 30 days, no questions asked.</p>
</details>
<summary>— the always-visible clickable line.- Everything else inside
<details>— hidden until the user clicks. - Add the
openattribute to have it expanded by default.
Why this is worth knowing: people build FAQ accordions, "read more" toggles, and collapsible sections with JavaScript and a library all the time — when the browser does it natively, accessibly, and keyboard-operable out of the box. It's a genuine "you didn't need that library" moment. FAQs, spoilers, collapsible details in docs — all one tag.
The limit to know: <details> gives you open/close for free, but it's hard to animate the expand smoothly with CSS (that's improving but still fiddly). If you need a polished slide-down animation, you may reach for JavaScript. For plain show/hide, <details> is the right, simplest tool.
18. dialog
<dialog> is a native modal or popup — again, with built-in behavior you'd otherwise script by hand:
<dialog id="confirm">
<p>Delete this item?</p>
<button id="cancel">Cancel</button>
<button id="ok">Delete</button>
</dialog>
<button id="open">Delete item</button>
const dialog = document.querySelector('#confirm');
document.querySelector('#open').onclick = () => dialog.showModal();
document.querySelector('#cancel').onclick = () => dialog.close();
showModal() opens it as a true modal, and the browser handles the hard parts for free:
- A backdrop behind it (dimmed background).
- Focus trapping — Tab stays inside the dialog instead of wandering to the page behind it.
- Escape closes it.
- Focus returns to the trigger when it closes.
Why this matters: every one of those behaviors is something people used to get wrong when hand-building modals with a <div> — the background stayed focusable, Escape didn't work, screen-reader users got lost behind the modal. <dialog> bakes the correct, accessible behavior in. Building an accessible modal from a <div> is genuinely hard; <dialog> gives it to you.
The catch: it needs a little JavaScript to open (showModal()) and close (close()) — it's not purely HTML like <details>. But the behavior is native; you only trigger it.
19. progress & meter
Two small semantic elements for showing quantities visually:
<progress> — task completion, moving toward done:
<progress value="70" max="100"></progress> <!-- 70% complete -->
Use it for things in progress toward completion — a file upload, a multi-step form, a loading bar.
<meter> — a measurement within a known range (not progress):
<meter value="0.8" min="0" max="1"></meter> <!-- 80% full -->
<meter value="3" min="0" max="10" low="3" high="7" optimum="5"></meter>
Use it for a static measurement — disk usage, a score, a rating, battery level. low/high/optimum let the browser color it (e.g. red when a value is in a bad range).
The decision: is the value moving toward completion? → <progress>. Is it a measurement on a scale? → <meter>. Disk that's 80% full is a <meter> (it's a state, not progressing toward "done"). A download at 80% is <progress> (it's heading to 100% and finishing).
Reality note: these are minor — you won't reach for them daily, and both are usually styled heavily with CSS or replaced by custom bars in real designs. Worth recognizing so you know they exist and what they mean semantically, not worth memorizing every attribute.
20. Building a Complete Form
Everything in this session comes together in one real, accessible signup form. This is the pattern to internalize:
<form action="/signup" method="POST">
<fieldset>
<legend>Create your account</legend>
<label for="name">Full name</label>
<input type="text" id="name" name="name" required autocomplete="name" />
<label for="email">Email</label>
<input type="email" id="email" name="email" required
autocomplete="email" placeholder="name@example.com" />
<label for="password">Password</label>
<input type="password" id="password" name="password"
required minlength="8" autocomplete="new-password" />
</fieldset>
<fieldset>
<legend>Choose a plan</legend>
<input type="radio" id="free" name="plan" value="free" checked />
<label for="free">Free</label>
<input type="radio" id="pro" name="plan" value="pro" />
<label for="pro">Pro</label>
</fieldset>
<label>
<input type="checkbox" name="terms" required />
I agree to the terms
</label>
<button type="submit">Sign up</button>
</form>
Read it against everything you learned:
method="POST"— it's private data, never GET.- Every input has a
name— or it won't be sent. - Every input has a
<label>bound byfor/id— accessible and clickable. - The radio group is one
<fieldset>with a<legend>— one question, three answers. - The radios share
name="plan"— that's what makes them mutually exclusive. required,minlength,type="email"— native validation for honest users.autocompletevalues — the browser fills it in one tap.type="submit"on the button — and it's a real<button>.
And the habit to carry forward, above all: this form validates on the client for convenience, but the server will validate every field again. The HTML makes the form usable and pleasant; it never makes it secure. That's the backend's job, and you'll meet it in every course after this one.