Forms deep dive: validation, fieldsets, and a real accessible checkout form
Part 10 covered a genuinely simple form. A real checkout form has more real requirements — grouped fields, actual validation, and error states that stay usable for someone relying on a screen reader. This part builds that form directly.
Real validation attributes, before any JavaScript is involved
<input type="email" name="email" required />
<input type="text" name="promo-code" minlength="4" maxlength="12" />
<input type="number" name="quantity" min="1" max="10" required />
<input type="tel" name="phone" pattern="[0-9]{10}" />required— the form won't submit until the field has a value; the browser shows its own real, built-in error message and focuses the field automaticallyminlength/maxlength— real character-count boundariesmin/max— real numeric (or date) boundariespattern— a regular expression the value must match, here requiring exactly 10 digits for a phone number
None of this requires a single line of JavaScript — this is genuine, built-in browser validation, running before the form ever reaches a server. It's not a replacement for real server-side validation (a visitor can always bypass client-side checks), but it's a real, immediate feedback layer that catches most honest mistakes before a submission ever leaves the browser.
Grouping real related fields: <fieldset> and <legend>
<form action="/checkout" method="post">
<fieldset>
<legend>Shipping address</legend>
<label for="name">Full name</label>
<input type="text" id="name" name="name" required />
<label for="address">Street address</label>
<input type="text" id="address" name="address" required />
<label for="zip">ZIP code</label>
<input type="text" id="zip" name="zip" pattern="[0-9]{5}" required />
</fieldset>
<fieldset>
<legend>Delivery frequency</legend>
<input type="radio" id="monthly" name="frequency" value="monthly" checked />
<label for="monthly">Every month</label>
<input type="radio" id="biweekly" name="frequency" value="biweekly" />
<label for="biweekly">Every two weeks</label>
</fieldset>
<button type="submit">Place order</button>
</form><fieldset> groups genuinely related fields, and <legend> names that group — a screen reader announces the legend as context for every field inside it, so "ZIP code" is announced within the "Shipping address" group, not as an isolated, ambiguous field. This matters specifically for radio buttons and checkboxes: without a <fieldset>/<legend> wrapping the "Delivery frequency" options, a screen reader user hitting the first radio button has no announced context for what the group of choices even represents.
Real, accessible error messages
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
required
aria-describedby="email-error"
/>
<span id="email-error" role="alert">Please enter a valid email address.</span>aria-describedby connects an input to additional descriptive text elsewhere on the page — here, a real error message — so a screen reader announces both the label and the error together when the field receives focus. role="alert" marks content as important enough to be announced immediately when it appears, which is exactly the behavior a real validation error needs; a visible-but-unannounced error message is invisible to a screen reader user even though it's right there on screen for a sighted one.
Styling a required field's error state with only a color change (red border, red text) and no textual indication of what's actually wrong. Color alone communicates nothing to a screen reader, and it's also a real, common accessibility failure for visitors with color blindness — a genuinely accessible error state needs real text (like the aria-describedby message above), with color as a secondary, supporting cue, never the only one.
Autocomplete: a genuinely underused, low-effort win
<input type="text" name="name" autocomplete="name" />
<input type="email" name="email" autocomplete="email" />
<input type="text" name="address" autocomplete="street-address" />The autocomplete attribute, using its real, standardized values, lets the browser correctly fill in a field from a visitor's saved information — genuinely useful on a real checkout form, where every field a visitor doesn't have to manually retype is a small, real reduction in checkout friction and abandonment.
Next: multimedia — the <audio> and <video> elements, and using them correctly for real content.