← Back to the Forms tutorial
Lesson

Forms, Visually

Forms are how people send data to a site — logging in, signing up, searching, checking out. This visual lesson covers the form element, every input type, labels, validation, grouping, and accessibility, with working fields you can type into throughout.

Step 1

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>
methodUse forBehavior
getSearches, filtersData appended to the URL (visible, bookmarkable).
postLogins, sign-ups, anything privateData sent in the request body (not in the URL).
Use post for sensitive data. Passwords and personal details should never ride in the URL where get would put them.
Step 2

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">
A placeholder is not a label. It vanishes when you type and isn't reliably read aloud. Always pair a visible <label> with the field; a placeholder is just an optional example.
Step 3

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; }
On a phone, 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.
Step 4

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; }
The trick with radios: options that share the same name become one group where only one can be selected. Give each its own id + matching label.
Step 5

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.
Step 6

Attributes that improve every field

AttributeDoes
nameRequired to submit — it's the key the data is sent under.
requiredWon't submit empty; browser blocks & prompts.
placeholderFaint example text inside the field.
valueA pre-filled or default value.
autocompleteLets the browser offer saved values (email, name).
min / max / stepNumeric & date limits.
maxlengthCaps characters typed.
disabled / readonlyGreys out / locks a field.
Don't forget name. A field with no name is invisible to the server — its value simply isn't sent.
Step 7

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">
Style validity with the :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.
Step 8

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.

Shipping speed
<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>
Step 9

Accessible, usable forms

The single biggest fix: connected labels. They power click-to-focus, screen-reader announcements, and bigger tap targets — all from one for/id pair.
Step 10

A complete contact form

Everything together — labels, types, required fields, a fieldset, and a working submit. It validates live:

Reason for contact

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

Where next

Keep going