Without notes, state yesterday’s main idea and one unresolved question.
Forms and accessible labels
HTML, CSS, Git, and the First Website
Objective
Build a usable form before adding any backend.
HTML is the mechanical structure, CSS is the physical layout and finish, and Git is the revision history for every design change.
- labels and controls
- input types
- native validation
Why this matters
A form is where a website stops being a document and starts being a machine — the visitor puts data in. You have no backend yet, so nothing is stored today. That is deliberate: a badly built form cannot be rescued by the server behind it, so you build the usable part first.
By the end you will have a contact form that someone who never touches a mouse can complete, and you will be able to prove it rather than assume it.
The controls
A control is an element the visitor operates. Four cover almost everything: <input> for
single-line values, <textarea> for multi-line text, <select> with <option> for a fixed list
of choices, and <button> for doing something. They live inside a <form>:
<form action="/contact" method="post">
<!-- controls go here -->
<button type="submit">Send message</button>
</form>
action is the URL the data is sent to; method is how (get puts values in the URL, post puts
them in the request body). With no server to receive it, leave action off for now — a form
without one submits back to the page it is on. Week 6 fills this in.
Each control needs a name: the key its value is sent under. A control without one is not
submitted at all. <button type="submit"> sends the form.
Labels, and the for/id pair
Every control needs a visible label connected to it in markup. Text sitting next to a box is not a connection.
<label for="email">Email address</label>
<input type="email" id="email" name="email" />
The connection is made when the label's for value matches the input's id. Those two
strings must be identical, and the id must be unique on the page. That is the whole mechanism,
and it is the single most-missed thing in beginner HTML.
What the connection buys you:
- A screen reader announces the label when focus reaches the control. Without it, the user hears "edit text, blank".
- Clicking the label focuses the control — a free test. If the cursor jumps into the box, the pair
is wired; if nothing happens,
forandiddo not match. - The click target gets bigger, which helps anyone with a tremor or a small screen.
`for`/`id` is the designator on the silkscreen
A silkscreen "R7" is useful only because exactly one component in the BOM is also called R7. The
printed text and the part are bound by a shared unique identifier, and a board with two parts
marked R7 is unserviceable. for and id are that binding — duplicate an id and the browser
binds the label to the first match, with the same ambiguity and the same result.
The name tag beside the doorbell
A block of flats has a row of identical buttons and a name card beside each. The button works without the card, but only for someone who already knows which is which.
A placeholder is not a label
<input placeholder="Email"> puts grey hint text inside the box. It vanishes the moment the
user types, so anyone interrupted loses the only clue about the field; its contrast is usually
too low; and some screen readers ignore it. Use a real <label>. A placeholder may sit
alongside one as an example of the format.
Input types
<input> changes behaviour entirely based on its type:
type |
Gives you |
|---|---|
text |
A plain single-line box (the default) |
email |
Email keyboard on phones, plus format checking |
tel |
Numeric keypad on phones |
number |
A numeric field accepting min, max, step |
date |
The browser's own date picker |
checkbox |
An independent on/off toggle |
radio |
One choice from a group sharing a name |
password |
Characters masked as you type |
The type is a usability decision, not a cosmetic one: type="email" on a phone produces a keyboard
with @ on it, and type="text" does not.
Native validation
The browser can check some things before submission, with no code from you:
required— the field cannot be left empty.type="email"ortype="url"— the value must look like one.minlength/maxlength— length limits on text.min/max— value limits on numbers and dates.pattern="[0-9]{4}"— the value must match that pattern.
If a rule fails, the browser blocks submission, focuses the offending field, and shows a small message near it. That wording varies by browser and language, so never write page text quoting it.
Browser validation is convenience, never security
Every check above runs on the visitor's machine, and the visitor controls that machine — DevTools
deletes a required attribute in two seconds, and a request can be sent without your form at
all. This is the Day 1 rule: rules enforced only in the browser are suggestions. You will
validate all of it again on the server in Week 6.
Walkthrough: a contact form
Create contact.html in profile-site with the same <head> and <nav> as your other pages, and
this inside <main>:
<section>
<h2>Contact me</h2>
<form>
<p>
<label for="name">Your name</label><br />
<input type="text" id="name" name="name" required autocomplete="name" />
</p>
<p>
<label for="email">Email address</label><br />
<input type="email" id="email" name="email" required autocomplete="email" />
</p>
<p>
<label for="topic">Topic</label><br />
<select id="topic" name="topic">
<option value="work">Work enquiry</option>
<option value="project">Question about a project</option>
<option value="other">Something else</option>
</select>
</p>
<p>
<label for="message">Message</label><br />
<textarea id="message" name="message" rows="6" minlength="10" required></textarea>
</p>
<button type="submit">Send message</button>
</form>
</section>
Open it and try three things. Click the words "Email address" — the cursor jumps into the box,
proving the pair is connected. Submit with everything empty — the browser refuses and focuses the
first empty required field. Type hello into the email field and submit — it refuses again,
because that is not email-shaped.
Add contact.html to the <nav> list on every page, as on Day 9.
Do the whole form with no mouse
Click once in the address bar, then press Tab repeatedly. Focus moves through every
control in order with a visible ring; Shift+Tab goes back; arrow keys
change the <select>. Reach and set every control this way and the form is keyboard-usable. On
macOS Safari, if Tab skips the select and button, enable Settings → Advanced → "Press Tab to
highlight each item on a webpage" — a browser setting, not a bug in your page.
Your turn
- Build
contact.htmlas above, typing rather than pasting, so thefor/idpairs go through your fingers. - Add a fifth control with a suitable type —
telfor a phone number, ordate. Give it a label, anid, and aname. - Confirm every control has a
<label>whoseformatches itsid, by clicking each label and watching focus move. - Do the keyboard pass from the
[!try]block above and note the tab order you observe. - Submit with valid data. The page reloads with the values in the URL as
?name=...&email=...— proof the data left the controls, and a preview of Week 6. - In DevTools → Elements, select an input and open the Accessibility pane. Its computed name should be your label text; blank means the pairing is broken.
You are done when
Every control has a label whose for matches its id, and you completed the entire form —
reaching and setting every control, then submitting — without touching the mouse once.
Reviewer mode, once your form works
Today's AI mode is skeptical reviewer: you hand over finished work and ask for concrete defects, not encouragement. A good review produces specific, actionable findings backed by evidence ("the topic select has no label, line 24") — not general praise and not a wholesale rewrite.
"Audit this HTML form for missing labels, unsuitable input types, and keyboard problems."
Verify each finding in the browser before changing anything. A review you accepted without checking is just someone else's guess.
Common pitfalls
forpointing atnameinstead ofid. Different attributes, different jobs.formatchesid, always.- Duplicate
idvalues after copying a field. The second label silently binds to the first input. Keep everyidunique. - Removing the focus ring with
outline: nonebecause it looks untidy. Keyboard users then have no idea where they are. Replace it with a visible style — never delete it. - No
nameattribute. The control looks fine and its value is never submitted. Nothing visibly breaks, so it is easy to miss.
Verify it yourself
Open today's reference, MDN's Accessibility material, and find what it says about forms and labelling.
- MDN mentions
aria-labelas an alternative when no visible label is possible. Find its guidance on when that is acceptable, and why a real<label>is still preferred. - Find MDN's description of what keyboard accessibility requires. Does your form meet every point?
Write both answers into notes/day-10.md, plus the one finding from your AI review that you
confirmed with your own eyes.
The hour
- 0–5 min Recall
- 5–20 min Learn
Read only the listed concept notes and official reference sections needed today.
- 20–48 min Build
Create a contact form with name, email, topic, and message. Test keyboard navigation.
- 48–55 min Explain and verify
Run the result, inspect evidence, and explain the data/control flow in your own words.
- 55–60 min Quiz and commit
Complete the quiz, record one lesson, and commit the verified change when applicable.
What to hand in
A keyboard-usable form with connected labels.
Working with AI today
Provide existing work and ask for concrete defects, risks, missing tests, and unsupported assumptions—not praise.
Audit this HTML form for missing labels, unsuitable input types, and keyboard problems.
References
End-of-day quiz
Explain-back gate
Pass the quiz above to unlock completion.
Quiz + explain-back checks required.