Accessible forms: labels, errors and the mistakes that fail WCAG
If I had to point at the single part of a website where accessibility most reliably falls apart, it's the forms. Not because forms are hard to make accessible — the rules are old and well understood — but because forms are where visual design fights hardest against assistive technology. The minimalist login with no visible labels, the error that's just a red outline, the custom dropdown a screen reader can't name. Here's how to build a form that actually passes, criterion by criterion.
Real labels, every time
The foundation is the humble <label>, programmatically tied to its input. WCAG covers this twice: 1.3.1 Info and Relationships (the association must be in the code, not just visual) and 3.3.2 Labels or Instructions (there must be a label or instruction at all). The clean way:
<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email">
The for matches the input's id. Now a screen reader announces "Email address, edit text," and clicking the label focuses the field. You can also wrap the input inside the label, but the explicit for/id pairing is the one I reach for — it survives layout changes.
The number-one form failure I see, by a mile: using placeholder text as the only label. Placeholders vanish the moment someone types, they're typically low-contrast, and they aren't a reliable accessible name. A placeholder can show an example format alongside a real label. It can never be the label.
Errors that explain themselves
Validation is the next place forms break. Two criteria:
- 3.3.1 Error Identification (Level A): when a field is wrong, say so in text, and say which field. A red border alone fails — it's color-only, invisible to a screen reader and to anyone who can't distinguish the color. This is the same don't-rely-on-color principle that trips up so much UI.
- 3.3.3 Error Suggestion (Level AA): if you know how to fix it, say how. "Invalid date" is weak; "Enter the date as MM/DD/YYYY" is accessible and just plain better UX.
In practice I pair two things: a short error summary at the top of the form with links that jump focus to each bad field, and an inline message next to each field. Wire the inline message to the input with aria-describedby so a screen reader reads the error when the field gets focus:
<input id="email" aria-describedby="email-err" aria-invalid="true">
<p id="email-err">Enter a valid email, like name@example.com</p>
Custom controls need name, role and value
The moment you replace a native control with a styled <div> — a fancy dropdown, a toggle, a custom checkbox — you take on 4.1.2 Name, Role, Value yourself. Assistive tech has to be able to tell what the control is (role), what it's called (name), and what state it's in (value/checked/expanded). Native elements give you all of that free. A div gives you nothing until you add the ARIA roles, states and keyboard handling by hand — and that's a lot of easy-to-get-wrong work. My honest advice: use the native <select>, <input type="checkbox"> and real <button> elements unless you have a strong reason not to. The most accessible custom widget is usually the one you didn't build.
Help the browser help the user: autocomplete
1.3.5 Identify Input Purpose (Level AA) asks you to declare what common fields are for, using the autocomplete attribute — autocomplete="email", "name", "tel", "street-address" and so on. This lets browsers autofill correctly and helps people with cognitive disabilities who rely on that autofill, and it's a two-second addition. There's no reason to skip it on a signup or checkout form.
What WCAG 2.2 added for forms
The 2.2 update brought a few criteria that land squarely on forms:
| Criterion | What it means for your form |
|---|---|
| 2.5.8 Target Size (Min) (AA) | Interactive targets at least 24×24 CSS pixels (with some exceptions) — so checkboxes, radios and submit buttons aren't tiny tap targets. |
| 3.3.7 Redundant Entry (A) | Don't make people re-type information they already entered earlier in the same process — autofill it or let them pick it. |
| 3.3.8 Accessible Authentication (AA) | Don't gate login behind a cognitive test like a puzzle CAPTCHA or "memorize this code." Allow password managers and paste. |
Test it the way a real user would
Here's the thing automated scanners can't fully do for forms: they'll catch a missing label or a color-only state, but they can't tell you the error message is confusing or the custom dropdown traps keyboard focus. So do the two-minute manual pass I do on every form:
- Unplug the mouse. Tab through the whole form. Can you reach and operate every field, see where focus is, and submit — without touching the mouse? Keyboard traps and invisible focus hide here.
- Trigger every error. Submit it broken and check each error is announced and explains the fix.
- Turn on a screen reader for five minutes. You'll hear your unlabeled fields immediately.
Forms are also the part of a site that changes most — a new field, a redesign, a third-party form embed — so a form that passed last quarter can quietly regress. This is exactly what an accessibility audit plus ongoing monitoring is for.
Catch form accessibility issues automatically
Perimeter's accessibility check flags missing form labels, color-only states and other WCAG issues across your pages — alongside your TLS, headers and DNS posture — and re-checks on a schedule, so a new form that ships without labels gets caught before a user (or an auditor) hits it. Free, no signup.
Monitor your accessibility → · Compliance plan $49/mo · or run a free accessibility check