Accessible Semantic Form Markup
A login or signup form is the highest-traffic interaction on most apps, and getting the semantic markup right pays dividends in screen-reader support, autofill quality, and form validation. This snippet covers a labelled email-and-password form, a form-with-error-summary using `aria-describedby`, and a multi-step fieldset pattern for grouped inputs.
600 views
6
<form action="/login" method="post">
<div>
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
required
>
</div>
<div>
<label for="password">Password</label>
<input
id="password"
name="password"
type="password"
autocomplete="current-password"
minlength="8"
required
>
</div>
<button type="submit">Sign in</button>
</form>Every input needs a <label for="id"> (not just placeholder text) so screen readers announce the field name and clicking the label focuses the input. autocomplete="email" and autocomplete="current-password" give the browser and password manager the right hints, which is what triggers autofill and one-tap login. The type="email" activates email-style mobile keyboards and basic format validation; minlength="8" adds a server-side-friendly client check. These tiny attributes make the difference between a clunky and a polished login.
<form>
<label for="username">Username</label>
<input
id="username"
name="username"
type="text"
aria-invalid="true"
aria-describedby="username-help username-error"
required
>
<p id="username-help">Letters, numbers, and dashes only.</p>
<p id="username-error" role="alert">That username is already taken.</p>
<button type="submit">Continue</button>
</form>aria-describedby links the input to one or more help-text and error elements. Screen readers read those messages right after the field name, which is what makes inline validation accessible. aria-invalid="true" flips the field into an error state for assistive tech, and role="alert" on the error container pushes the message to a live region so updates are announced. The combination is what good error UX looks like on every form: the message is visible, programmatically associated, and announced when it changes.
<form>
<fieldset>
<legend>Shipping address</legend>
<label for="line1">Address</label>
<input id="line1" name="line1" autocomplete="address-line1" required>
<label for="city">City</label>
<input id="city" name="city" autocomplete="address-level2" required>
<label for="zip">ZIP</label>
<input id="zip" name="zip" inputmode="numeric" autocomplete="postal-code" required>
</fieldset>
<fieldset>
<legend>Delivery options</legend>
<label><input type="radio" name="speed" value="standard" checked> Standard</label>
<label><input type="radio" name="speed" value="express"> Express</label>
</fieldset>
<button type="submit">Continue</button>
</form><fieldset> plus <legend> is the right wrapper for groups of related inputs (an address, a credit card, a set of radio buttons). Screen readers announce the legend before each child input, which gives blind users the same context that sighted users get from a heading. Grouping radio buttons inside a single fieldset is mandatory for accessibility, since the legend tells the user what the group is choosing between. The inputmode="numeric" plus autocomplete="postal-code" combination unlocks the right mobile keyboard and the saved postal code on the user's device.
