HTML Forms
An HTML form is the standard way a web page collects information from a visitor — a search query, a login, a shipping address, a survey response — and sends it somewhere for processing. Every text box, checkbox, dropdown, and submit button you’ve ever clicked on the web lives inside a <form> element. Understanding forms means understanding how the browser gathers all those individual controls into one package and delivers it to a server.
Overview: How Forms Work
A form is a container element, <form>, that wraps one or more form controls: elements like <input>, <textarea>, <select>, and <button>. The browser treats everything inside the form as a single unit for the purposes of submission. When a user triggers submission — usually by clicking a submit button or pressing Enter inside a text field — the browser gathers the current value of every control that has a name attribute, packages those name/value pairs, and sends them to whatever URL is specified in the form’s action attribute, using the HTTP method specified in its method attribute.
This is important: a control without a name attribute is never submitted. The name is the key in the eventual name=value pair; the current content of the field (what the user typed, which checkbox is checked, which option is selected) is the value. If you forget name, the browser still displays and lets users interact with the control, but it silently disappears from the submitted data — one of the most common form bugs.
In the DOM, <form> is a normal block-level container with no default visual styling beyond a small margin in some browsers — it does not draw a border or box around its contents. All the visible “boxiness” of form controls (the border on a text input, the shape of a button) comes from the browser’s default user-agent stylesheet for those specific elements, not from the <form> itself. Styling forms further is the job of CSS, covered in the CSS course — this lesson focuses purely on structure and behavior.
Two submission methods matter most:
- GET (the default) appends the form data to the
actionURL as a query string, e.g.?q=cats&page=2. It’s visible in the address bar, bookmarkable, and appropriate for searches or filters — but unsuitable for sensitive data like passwords, and has practical length limits. - POST sends the data in the HTTP request body, invisible in the URL. It’s the correct choice for anything that creates, changes, or deletes data, or that includes sensitive or large amounts of information (like file uploads).
Syntax
<form action="/submit-url" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<button type="submit">Send</button>
</form>
| Attribute | Belongs to | Purpose |
|---|---|---|
action |
<form> |
URL that receives the submitted data. If omitted, the form submits to the current page’s own URL. |
method |
<form> |
HTTP method used to submit: get (default) or post. |
name |
form controls | The key used when the control’s value is submitted. Required for the control’s data to be sent at all. |
id |
form controls | Unique identifier, used to associate a <label> with the control via the label’s for attribute. |
type |
<input> |
Determines what kind of control renders: text, email, password, checkbox, radio, number, date, submit, and many more. |
required |
form controls | Boolean attribute; the browser blocks submission and shows a validation message if the field is empty. |
placeholder |
text-like <input>/<textarea> |
Faint hint text shown when the field is empty — not a substitute for a <label>. |
Examples
Example 1: A minimal login form
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<button type="submit">Log In</button>
</form>
Result: The browser renders two labeled fields stacked vertically (exact layout depends on default styling): a text box for the username and a password box that masks typed characters as dots, followed by a “Log In” button. Clicking either label moves keyboard focus into its associated field. If either field is left empty and the button is clicked, the browser shows a small native validation bubble (“Please fill out this field”) and blocks submission instead of sending the request.
This example shows the two most important accessibility habits: every field has a <label> whose for matches the field’s id, and every field has a name so its data actually reaches the server as username=...&password=....
Example 2: A realistic registration form with mixed control types
<form action="/register" method="post">
<fieldset>
<legend>Account details</legend>
<label for="fullname">Full name:</label>
<input type="text" id="fullname" name="fullname" required>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<label for="country">Country:</label>
<select id="country" name="country">
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
</select>
<label for="bio">Short bio:</label>
<textarea id="bio" name="bio" rows="3"></textarea>
<p>
<input type="checkbox" id="newsletter" name="newsletter" value="yes">
<label for="newsletter">Subscribe to newsletter</label>
</p>
</fieldset>
<button type="submit">Create Account</button>
</form>
Result: The browser draws a bordered box (from <fieldset>) with the caption “Account details” (from <legend>) breaking the border at the top. Inside it: a labeled text field, a labeled email field (which on mobile devices triggers an email-optimized keyboard), a dropdown listing three countries, a multi-line text area for the bio, and a checkbox with its own label. A “Create Account” button sits outside the fieldset. Checking the checkbox and submitting sends newsletter=yes; leaving it unchecked omits newsletter from the submission entirely — unchecked checkboxes are not sent at all, not sent as “false” or empty.
<fieldset> and <legend> group related controls visually and, more importantly, semantically — screen readers announce the legend text when a user tabs into any control inside the fieldset, giving context that an isolated label can’t provide.
Example 3: Radio buttons and number/date inputs with a GET search form
<form action="/search" method="get">
<label for="q">Search:</label>
<input type="text" id="q" name="q">
<p>Sort by:</p>
<input type="radio" id="sort-relevance" name="sort" value="relevance" checked>
<label for="sort-relevance">Relevance</label>
<input type="radio" id="sort-date" name="sort" value="date">
<label for="sort-date">Date</label>
<label for="max-results">Max results:</label>
<input type="number" id="max-results" name="max" min="1" max="100" value="20">
<button type="submit">Search</button>
</form>
Result: A search box appears alongside two radio buttons (“Relevance” pre-selected via checked) and a number spinner defaulting to 20, with up/down arrows and browser-enforced bounds between 1 and 100. Because method="get", submitting with the query “cats” navigates to a URL like /search?q=cats&sort=relevance&max=20 — visible, bookmarkable, and shareable, which is exactly right for a search page. Note that radio buttons sharing the same name (sort) form a mutually exclusive group — selecting one automatically deselects the other, and only the checked one’s value is submitted.
How It Works Step by Step
When the browser parses a <form> and its controls, several things happen behind the scenes:
- Each control becomes a node in the DOM tree, and the browser also maintains an internal, ordered list of the form’s “submittable elements” — controls with a valid
namethat are not disabled. - When a submit action fires (button click, Enter key in a single-line text field, or JavaScript calling
form.submit()), the browser first runs constraint validation: it checks every field withrequired,min/max,pattern, or type-specific rules (like a valid email shape fortype="email"). If any check fails, submission is cancelled and the browser focuses the first invalid field, showing a native tooltip. - If validation passes, the browser walks the submittable elements in document order and constructs the data set — one name/value pair per control (multiple pairs for multi-select lists or groups of checked checkboxes sharing a name).
- That data set is encoded — as a URL query string for GET, or in the request body (typically
application/x-www-form-urlencoded, ormultipart/form-datawhen a file input is present) for POST — and sent as an HTTP request to theactionURL. - The browser then navigates to whatever response the server sends back, exactly as it would for clicking a link — a full page load, unless JavaScript intercepts the submit event.
Common Mistakes
Mistake 1: Forgetting the name attribute
<form action="/subscribe" method="post">
<label for="email">Email:</label>
<input type="email" id="email">
<button type="submit">Subscribe</button>
</form>
This looks correct and renders fine, but the input has no name. On submission, the browser sends an empty payload — the field’s value is dropped entirely, and the server receives nothing to process. The id is only for the label association and JavaScript/CSS targeting; it does not double as the submission key.
<form action="/subscribe" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
Adding name="email" fixes it: the server now receives email=someone%40example.com.
Mistake 2: A label with no association to its field
<p>Age:</p>
<input type="number" name="age">
Here “Age:” is just a plain paragraph, not a <label>, so clicking the text does nothing and screen reader users hear an unlabeled number field with no indication of what it’s for. The fix is to use an actual <label> tied to the input via matching for/id:
<label for="age">Age:</label>
<input type="number" id="age" name="age">
Now clicking or tapping the label text focuses the number field — larger click targets and correct accessibility semantics for free.
Mistake 3: Nesting one form inside another
<form action="/outer" method="post">
<input type="text" name="outer-field">
<form action="/inner" method="post">
<input type="text" name="inner-field">
</form>
</form>
HTML forbids nesting <form> elements — the parser will not build the nested structure you wrote, and behavior becomes inconsistent across browsers. If a page needs two independent submissions, place two separate, non-nested <form> elements side by side instead.
Best Practices
- Give every form control a
nameattribute, or its value will never be submitted. - Always pair inputs with a real
<label>using matchingfor/id, even when aplaceholderis present — placeholder text disappears once typing starts and is not a reliable substitute. - Use
method="get"for read-only actions like search and filtering, andmethod="post"for anything that changes data or includes sensitive information. - Choose the most specific
typeavailable (email,tel,number,date) — it gets you free client-side validation and better on-screen keyboards on mobile. - Group related fields with
<fieldset>and describe the group with<legend>, especially for radio button groups. - Rely on built-in validation attributes (
required,min,max,pattern) as a first line of defense, but always validate again on the server — client-side checks can be bypassed. - Never nest
<form>elements; use separate forms for separate submissions.
Practice Exercises
- Build a simple feedback form with a text input for “Name”, an email input for “Email”, a
<textarea>for “Comments”, and a submit button — make sure every field has a properly associated<label>and anameattribute. - Add a group of three radio buttons for “How did you hear about us?” (e.g. Search, Friend, Social Media) to the form above, wrapped in a
<fieldset>with an appropriate<legend>. Make sure all three radios share the samenameso only one can be selected at a time. - Take the search form from Example 3 and change its
methodfromgettopost. Predict, then check by reasoning through the submission steps: will the search terms still appear in the address bar after submitting? Why or why not?
Summary
- A
<form>groups controls and defines where (action) and how (method) their data is sent on submission. - Only controls with a
nameattribute get submitted; thenamebecomes the key and the current value becomes the value in the submitted data. GETputs data in the URL (good for search/filter);POSTputs data in the request body (good for sensitive or data-changing submissions).- Every field needs a
<label>associated via matchingfor/idattributes for accessibility and usability. <fieldset>and<legend>group and describe related controls, especially radio button sets.- Built-in attributes like
required,min,max, and type-specific formats give free client-side validation, but server-side validation is still essential. - Forms cannot be nested inside other forms.
