Overview
Forms are one of the most common sources of accessibility failures. Screen reader users rely on properly associated labels to understand what each field expects. Without labels, a user hears only “edit text” with no context. Error messages must also be programmatically associated with the field that failed.
WCAG Criteria:
- 1.3.1 Info and Relationships — structure must be conveyed programmatically
- 3.3.1 Error Identification — errors must be described in text
- 3.3.2 Labels or Instructions — inputs must have labels
- 4.1.2 Name, Role, Value — interactive elements must expose their role and state
Key requirements:
- Every input must have a
<label>associated viafor/id - Required fields must use
aria-required="true"(or therequiredattribute) - Hint text should be linked via
aria-describedby - Error messages must be associated with the invalid field via
aria-describedbyandaria-invalid="true" - Use
<button type="submit">, not a<div>or<a>
Labeled Fields
Labeled Fields vs. Placeholder-Only
Inaccessible
<!-- Placeholders disappear when user types — no persistent label -->
<input placeholder="Full name">
<input placeholder="Email address">
<div class="btn">Sign Up</div>Live Preview
Sign Up
Accessible
<form>
<div>
<label for="forms-good-name">Full name</label>
<input
id="forms-good-name"
type="text"
required
aria-required="true"
autocomplete="name"
/>
</div>
<div>
<label for="forms-good-email">Email address</label>
<input
id="forms-good-email"
type="email"
required
aria-required="true"
aria-describedby="forms-good-email-hint"
autocomplete="email"
/>
<span id="forms-good-email-hint">We'll send a confirmation to this address.</span>
</div>
<button type="submit">Sign Up</button>
</form>Live Preview
What’s wrong with placeholder-only inputs?
- Placeholders are not visible once the user starts typing
- Screen readers may not announce placeholder text as the field’s label
- The
<div>button is not keyboard accessible and has nobuttonrole
What the screen reader announces:
| Version | Announcement on focus |
|---|---|
| Inaccessible (placeholder) | “edit text” or “blank” — no label |
| Accessible (label) | “Full name, required, edit text” |
| With hint | ”Email address, required, edit text — We’ll send a confirmation to this address.” |
Error Handling
Associated Error Messages
Inaccessible
<!-- Error text exists visually but is not linked to the field -->
<label for="forms-bad-pw">Password</label>
<input id="forms-bad-pw" type="password" style="border-color:red">
<p style="color:red">Must be at least 8 characters.</p>Live Preview
Must be at least 8 characters.
Accessible
<label for="forms-good-pw">Password</label>
<input
id="forms-good-pw"
type="password"
aria-invalid="true"
aria-describedby="forms-pw-error"
/>
<span id="forms-pw-error" role="alert" style="color:red">
Must be at least 8 characters.
</span>Live Preview
Error: Must be at least 8 characters.
What’s wrong without aria-invalid and aria-describedby?
- The red border is a visual-only cue — not perceivable by screen reader users
- The error paragraph is nearby in the DOM but not programmatically linked to the field
- When the user focuses the input, the error is never read
What the screen reader announces:
| Version | Announcement on focus |
|---|---|
| Inaccessible | ”Password, edit text” — error never mentioned |
| Accessible | ”Password, invalid entry, edit text — Must be at least 8 characters.” |