The Complete Guide to Accessible Forms
Forms are where accessibility fails most and costs most. Labels, errors, grouping, required fields and validation — the patterns that work, with the markup.
More accessibility failures live in forms than anywhere else, and they cost more than failures anywhere else. A badly labelled heading is an irritation. A form someone cannot complete is a permit not applied for, a bill not paid, an application not submitted, a sale not made.
This is the markup that works, the patterns that break, and why.
Every input needs a real label
The single most common form failure, and the easiest to fix.
<!-- Broken: no programmatic association -->
<span>Email address</span><br>
<input type="email" name="email">
<!-- Broken: placeholder is not a label -->
<input type="email" name="email" placeholder="Email address">
<!-- Correct -->
<label for="email">Email address</label>
<input type="email" id="email" name="email">
The for attribute must match the input's id exactly. When it does, three things happen: screen readers announce the label when the field receives focus, clicking the label focuses the field, and the tap target grows to include the label text.
Test it in one second: click the label text. If the field focuses, the association works. If nothing happens, it does not — and no screen reader will announce it either.
Why placeholders fail as labels
Placeholder text disappears the moment someone types. A user who is interrupted mid-form returns to a field with no indication of what belongs in it. Placeholder text also typically fails contrast requirements, is not reliably announced, and vanishes for anyone using autofill.
Use a placeholder for a format hint if you must, alongside a real label — or better, put the format in the label itself: Date of birth (DD/MM/YYYY).
When a visible label genuinely cannot be shown
Search fields are the usual honest case. Use aria-label:
<input type="search" id="q" name="q" aria-label="Search this site">
<button type="submit">Search</button>
One rule if you do: whatever is visible on a control must be contained in its accessible name (WCAG 2.5.3). A button reading "Submit application" with aria-label="Send" breaks speech input — the user says what they can see, and nothing happens.
Group related fields
Radio buttons and checkboxes that belong together need to be announced together, or the user hears a series of unexplained options.
<fieldset>
<legend>How would you like to be contacted?</legend>
<label><input type="radio" name="contact" value="email"> Email</label>
<label><input type="radio" name="contact" value="phone"> Phone</label>
<label><input type="radio" name="contact" value="post"> Post</label>
</fieldset>
Without the fieldset and legend, a screen reader user reaching the second option hears "Phone, radio button, two of three" with no idea what the question was.
The same applies to related text inputs — an address block, a date split across three fields.
Mark required fields in more than one way
<label for="name">
Full name <span class="required">(required)</span>
</label>
<input type="text" id="name" name="name" required aria-required="true">
A red asterisk alone fails WCAG 1.4.1: colour is the only thing carrying the information, and the meaning of * is conventional rather than stated. Use the word, and put a legend at the top of the form if you use a symbol as well.
The native required attribute gives you browser validation and is announced by screen readers. Add aria-required="true" when you are handling validation yourself and suppressing the native behaviour.
Errors: the part that decides whether the form is usable
Three things have to be true. Most forms get one.
1. The error must be in text
<!-- Broken: colour is the only signal -->
<input type="email" id="email" class="field-error-red">
<!-- Correct -->
<label for="email">Email address</label>
<input type="email" id="email" name="email"
aria-invalid="true" aria-describedby="email-error">
<span id="email-error" class="field-error">
Enter an email address in the format name@example.com
</span>
aria-describedby ties the message to the field, so a screen reader user hears it when they reach the field rather than having to hunt for it. aria-invalid="true" announces the field as being in an error state.
2. The error must say how to fix it
"Invalid input" fails WCAG 3.3.3. So does "Error in field 3". Compare:
| Unhelpful | Useful |
|---|---|
| Invalid date | Enter the date as DD/MM/YYYY, for example 26/04/2027 |
| Invalid password | Password must be at least 12 characters |
| Error | Enter a postcode — we use it to find your local office |
If you know what is wrong, say it. The only reason to be vague is security, and that applies to login failures, not to date formats.
3. The error must be announced, not just displayed
A form that validates with JavaScript and renders errors into the DOM shows nothing to a screen reader user unless you tell it to. Two approaches:
Move focus to the first error. Simple, reliable, and it tells the user exactly where the problem is.
const firstError = form.querySelector('[aria-invalid="true"]');
if (firstError) firstError.focus();
Or announce a summary in a live region:
<div role="alert" id="form-errors"></div>
Anything written into an element with role="alert" is announced immediately. Use it for a count and a summary — "3 problems with this form" — and still associate each message with its field.
For a long form, do both: an error summary at the top with links to each field, and a message at each field. That is the pattern government services converged on, and it converged for good reasons.
Do not disable the submit button
A disabled submit button is not focusable, gives no explanation, and leaves a keyboard user pressing Tab past a control they cannot reach or query. They have no way to find out what is missing.
Leave it enabled. Let them submit. Then show them what needs fixing. That is what error handling is for.
Autocomplete is an accessibility feature
<label for="name">Full name</label>
<input type="text" id="name" name="name" autocomplete="name">
<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="tel">Phone number</label>
<input type="tel" id="tel" name="tel" autocomplete="tel">
WCAG 2.1 added criterion 1.3.5 Identify Input Purpose for exactly this. Fields collecting information about the user need programmatic autocomplete values, so browsers and assistive tools can fill them or relabel them with symbols the user recognises. It is one attribute, and it removes the need to type an address for anyone who finds typing difficult.
Do not require re-entry
WCAG 2.2 added 3.3.7 Redundant Entry: information the user already provided in the same process must be auto-populated or available to select, not retyped. Every multi-step application that asks for an address three times now fails a Level A criterion.
The same release added 3.3.8 Accessible Authentication, which has one practical implication most sites violate: do not block paste in password fields. Blocking paste breaks password managers, which forces memorisation, which is the cognitive function test the criterion exists to prevent.
Timeouts need a warning and a way out
Session timeouts on banking, benefits and patient portals are a classic failure — the session expires silently and everything entered is gone. WCAG 2.2.1 requires that the user can turn the limit off, adjust it, or extend it, with a warning and at least twenty seconds to respond.
Someone using a screen reader, or switch access, or simply reading carefully, takes longer. A timeout tuned to an average user excludes everyone slower than average.
Make the targets big enough
WCAG 2.2 criterion 2.5.8 requires interactive targets to be at least 24 by 24 CSS pixels, or spaced far enough apart to reach that effective size. Checkboxes and radio buttons are the usual offenders — the native control is small, so wrap it in the label:
<label class="choice">
<input type="checkbox" name="terms" id="terms">
<span>I agree to the terms</span>
</label>
.choice {
display: flex;
align-items: center;
gap: 10px;
min-height: 44px;
}
The whole label becomes the target, which helps everyone on a phone.
CAPTCHA is usually an accessibility failure
Image-recognition CAPTCHA is a cognitive function test with no alternative, which is what WCAG 2.2's 3.3.8 was written to address. Audio alternatives are notoriously unreliable.
If you need bot protection, prefer approaches that do not test the user: honeypot fields, timing heuristics, or a service that only challenges suspicious traffic. If you must use a visible challenge, provide a genuinely non-cognitive alternative and a route to a human.
A checklist you can run
For every form you own:
- Clicking each label focuses its field
- No placeholder is doing a label's job
- Related radios and checkboxes sit in a
fieldsetwith alegend - Required fields say "required" in text
- The whole form is completable with a keyboard alone
- Focus is visible at every stop
- Submitting with errors produces text messages that say how to fix them
- Errors are tied to fields with
aria-describedbyand markedaria-invalid - Focus moves to the first error, or a
role="alert"summary announces it - The submit button is never disabled
autocompleteis set on fields about the user- Nothing is re-entered that was entered earlier
- Paste works in password fields
- Timeouts warn and can be extended
- Targets are at least 24×24 pixels
- The confirmation is announced, not just displayed
Run that against your most important form. If you would rather have it done properly across everything you own, that is what an audit covers — and our free checker will find the labelling failures in about ten seconds.
Related
How to Test a Website for Accessibility
The manual testing methodology we use on client audits, published in full — keyboard, screen reader, zoom, forms and media, with what to record and what it means.
LeadershipWho Actually Owns Accessibility
Saying "everyone owns it" means nobody does. The organizations that stay conformant have one accountable person and a short list of duties per role.
BusinessThe Business Case for Accessibility
Legal risk gets accessibility funded. It's rarely what makes it worth doing. The operational returns are more reliable and easier to measure.