The <form> element
A form wraps controls and defines where their data goes. Two attributes matter most: action (the URL that receives the data) and method (how it's sent).
<form action="/signup" method="post"> <!-- inputs go here --> <button>Sign up</button> </form>
| method | Use for | Behavior |
|---|---|---|
| get | Searches, filters | Data appended to the URL (visible, bookmarkable). |
| post | Logins, sign-ups, anything private | Data sent in the request body (not in the URL). |
post for sensitive data. Passwords and personal details should never ride in the URL where get would put them.Labels: the most important habit
Every input needs a <label>. Connect them by matching the label's for to the input's id. Now clicking the label focuses the field, and screen readers announce it.
<label for="email">Email address</label> <input type="email" id="email" name="email">
<label> with the field; a placeholder is just an optional example.Input types do real work
The type attribute changes the keyboard, validation, and picker the browser shows — especially on phones. Try these live:
HTML
<div class="field"> <label for="email">type="email"</label> <input type="email" id="email" placeholder="you@example.com"> </div> <div class="field"> <label for="pass">type="password"</label> <input type="password" id="pass" placeholder="••••••••"> </div> <div class="field"> <label for="num">type="number"</label> <input type="number" id="num" min="0" max="10" placeholder="0–10"> </div> <div class="field"> <label for="date">type="date"</label> <input type="date" id="date"> </div> <div class="field"> <label for="tel">type="tel"</label> <input type="tel" id="tel" placeholder="(555) 123-4567"> </div> <div class="field"> <label for="search">type="search"</label> <input type="search" id="search" placeholder="Search…"> </div>
CSS
label { display: block; font-weight: 700; font-size: .85rem; margin-bottom: 5px; color: #1f2433; } input { width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: .92rem; font-family: inherit; background: #fff; color: #1f2433; } input:focus { outline: 2px solid #10b981; outline-offset: 1px; border-color: #10b981; } .field { margin-bottom: 14px; }
type="email" shows an @-key keyboard and type="number" shows a number pad. Picking the right type is a free UX and accessibility win — no JavaScript required.Letting users choose
Radio buttons — pick one (share a name)
Checkboxes — pick any
HTML
<!-- Radio buttons: same name = one group, pick one --> <div class="choice"><input type="radio" id="r1" name="plan" checked><label for="r1">Free</label></div> <div class="choice"><input type="radio" id="r2" name="plan"><label for="r2">Pro</label></div> <div class="choice"><input type="radio" id="r3" name="plan"><label for="r3">Team</label></div> <!-- Checkboxes: pick any number --> <div class="choice"><input type="checkbox" id="c1" checked><label for="c1">Email me updates</label></div> <div class="choice"><input type="checkbox" id="c2"><label for="c2">Send me the newsletter</label></div>
CSS
.choice { display: flex; align-items: center; gap: 8px; font-weight: 500; margin-bottom: 6px; } .choice input { width: auto; } .choice label { display: inline; margin: 0; font-weight: 500; }
name become one group where only one can be selected. Give each its own id + matching label.Textarea, select & buttons
HTML
<div class="field"> <label for="msg">Message (multi-line)</label> <textarea id="msg" placeholder="Type a few lines…"></textarea> </div> <div class="field"> <label for="country">Country (dropdown)</label> <select id="country"> <option value="">Choose…</option> <option>United States</option> <option>Canada</option> <option>Mexico</option> </select> </div> <div class="field"> <button type="button" class="btn">Submit button</button> <button type="button" class="btn reset">Reset</button> </div>
CSS
label { display: block; font-weight: 700; font-size: .85rem; margin-bottom: 5px; color: #1f2433; } textarea, select { width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: .92rem; font-family: inherit; background: #fff; color: #1f2433; } textarea { min-height: 80px; resize: vertical; } textarea:focus, select:focus { outline: 2px solid #10b981; outline-offset: 1px; border-color: #10b981; } .field { margin-bottom: 14px; } .btn { background: #10b981; color: #fff; border: 0; border-radius: 8px; padding: 11px 20px; font-weight: 700; cursor: pointer; font-size: .9rem; font-family: inherit; } .btn:hover { background: #059669; } .btn.reset { background: #fff; color: #475569; border: 1px solid #cbd5e1; margin-left: 8px; }
<textarea> needs a closing tag (unlike <input>). A <button> inside a form submits by default — set type="button" when you don't want that.Attributes that improve every field
| Attribute | Does |
|---|---|
| name | Required to submit — it's the key the data is sent under. |
| required | Won't submit empty; browser blocks & prompts. |
| placeholder | Faint example text inside the field. |
| value | A pre-filled or default value. |
| autocomplete | Lets the browser offer saved values (email, name). |
| min / max / step | Numeric & date limits. |
| maxlength | Caps characters typed. |
| disabled / readonly | Greys out / locks a field. |
name. A field with no name is invisible to the server — its value simply isn't sent.Built-in validation
The browser validates for free — no JavaScript. Add required, use the right type, or set a pattern, and invalid submissions are blocked with a prompt. This mini form really validates — try submitting it empty or with a bad email:
<input type="email" required> <input pattern="[0-9]{5}" title="Five digits">
:valid and :invalid pseudo-classes (the green/red borders above appear as you type). Server-side validation is still required too — never trust the browser alone for security.Grouping with fieldset & legend
Wrap related fields in a <fieldset> with a <legend> caption. It visually groups them and tells screen readers the fields belong together — essential for radio groups.
<fieldset> <legend>Shipping speed</legend> <input type="radio" id="s1" name="ship" checked> <label for="s1">Standard (free)</label> <input type="radio" id="s2" name="ship"> <label for="s2">Express (+$9)</label> </fieldset>
Accessible, usable forms
- Pair every control with a real
<label>(Step 2). - Mark errors with text and an icon, not color alone — “Email is required,” not just a red box.
- Keep a visible focus outline so keyboard users can see where they are (don't remove it).
- Group radios/checkboxes in a
<fieldset>with a<legend>. - Use the right
type&autocompleteso phones and password managers help. - Ask for the least data you need — every extra field loses users.
for/id pair.A complete contact form
Everything together — labels, types, required fields, a fieldset, and a working submit. It validates live:
HTML
<form> <div class="field"> <label for="name">Name</label> <input type="text" id="name" name="name" placeholder=" " required autocomplete="name"> </div> <div class="field"> <label for="email">Email</label> <input type="email" id="email" name="email" placeholder=" " required autocomplete="email"> </div> <div class="field"> <fieldset> <legend>Reason for contact</legend> <div class="choice"><input type="radio" id="r1" name="reason" checked><label for="r1">Question</label></div> <div class="choice"><input type="radio" id="r2" name="reason"><label for="r2">Feedback</label></div> <div class="choice"><input type="radio" id="r3" name="reason"><label for="r3">Bug report</label></div> </fieldset> </div> <div class="field"> <label for="message">Message</label> <textarea id="message" name="message" placeholder=" " required></textarea> </div> <div class="field"> <div class="choice"><input type="checkbox" id="copy"><label for="copy">Email me a copy</label></div> </div> <button type="submit" class="btn">Send message</button> </form>
CSS
label { display: block; font-weight: 700; font-size: .85rem; margin-bottom: 5px; color: #1f2433; } input[type=text], input[type=email], textarea { width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: .92rem; font-family: inherit; background: #fff; color: #1f2433; } input:focus, textarea:focus { outline: 2px solid #10b981; outline-offset: 1px; border-color: #10b981; } textarea { min-height: 80px; resize: vertical; } .field { margin-bottom: 14px; } fieldset { border: 1px solid #cbd5e1; border-radius: 10px; padding: 14px; } legend { font-weight: 700; padding: 0 8px; font-size: .9rem; } .choice { display: flex; align-items: center; gap: 8px; font-weight: 500; margin-bottom: 6px; } .choice input { width: auto; } .choice label { display: inline; margin: 0; font-weight: 500; } .btn { background: #10b981; color: #fff; border: 0; border-radius: 8px; padding: 11px 20px; font-weight: 700; cursor: pointer; font-size: .9rem; font-family: inherit; } .btn:hover { background: #059669; } input:invalid:not(:placeholder-shown) { border-color: #ef4444; } input:valid:not(:placeholder-shown) { border-color: #10b981; }
Form checklist
- Every control has a connected
<label for> - Every field has a
nameso its data is sent - The right
typeis used (email, number, tel, date…) - Required fields marked
required; errors shown in text + icon - Radio/checkbox groups wrapped in a
<fieldset>+<legend> - Sensitive forms use
method="post"; focus outline is visible