A form is a task, not just content. To finish it, a person has to know what each
field is for, find out when something is wrong and how to fix it, understand how the
choices relate, and — ideally — let the browser fill in the data it already holds.
Break any one of those and the task stalls: a field announced as bare “edit text”,
an error spoken only in red, a group of radios with no shared question, or a name
field the browser can’t autofill all push effort back onto the user, and for some
people they close the door entirely.
This lesson works through the four defects behind the majority of real-world form
failures. Each one is small to fix and uses plain HTML you already have — the wins
come from wiring the parts together correctly, not from adding ARIA on top.
What you’ll learn
How to give every input a real, programmatic label; how to
associate an error with its field and announce it the moment it appears; how to
group related radios and checkboxes with fieldset and
legend; and how to add the right autocomplete tokens so
personal-data fields fill themselves.
Standards this lesson maps to
Standard
Criterion
Level
What it requires
WCAG 2.2
1.3.1 Info and Relationships
A
A field’s label, its grouping, and its error are conveyed in the markup, not by visual layout alone.
WCAG 2.2
3.3.1 Error Identification
A
When input is rejected, the field in error is identified and the problem is described in text.
WCAG 2.2
3.3.2 Labels or Instructions
A
Labels or instructions are provided when content requires user input.
WCAG 2.2
3.3.3 Error Suggestion
AA
If a fix is known, it is suggested to the user, unless doing so risks security.
WCAG 2.2
1.3.5 Identify Input Purpose
AA
The purpose of fields collecting user data is programmatically set (autocomplete tokens).
WCAG 2.2
4.1.2 Name, Role, Value
A
Each field exposes a name and role; its state and value are available to assistive technology.
WCAG 2.2
4.1.3 Status Messages
AA
Status and error messages are exposed to assistive tech without moving focus.
EN 301 549
9.3.3 / 9.4.1.2 (incorporates WCAG)
—
European harmonised standard; references the WCAG A/AA set including form criteria.
Section 508
502.3 / 504 (incorporates WCAG A & AA)
—
US federal ICT must meet WCAG 2.0 Level A and AA, including labels and errors.
ADA Title II
WCAG 2.1 AA (DOJ rule)
AA
US state/local government web content must conform to WCAG 2.1 AA.
The four problems we’ll fix
Each card below isolates one common form defect. For every issue you get a
plain-language statement of the problem, a Bad example (shown as
escaped, non-running code so it can’t harm this page), a Good
example, the copyable Code, and an ordered fix.
Input with no real label
WCAG 2.2 · 1.3.1A3.3.2A4.1.2AEN 301 549
A placeholder is not a label. It’s grey,
low-contrast hint text that disappears the instant the user types, so it fails as
an instruction and is unreliable as a name. A field with only a placeholder is
announced by many screen readers as just “edit text”, and once the user starts
typing they have nothing left to remind them what the field was for. A real
<label> stays visible, is read on focus, and — because clicking
it moves focus into the field — gives motor and touch users a larger target.
Bad
The placeholder is doing the label’s job. There is no programmatic name, and
the hint vanishes the moment the user types.
A visible <label> is tied to the input by matching
for and id. The placeholder, if kept at all, only
shows an example format — it never replaces the label.
When the design genuinely has no room for a visible label, use a visually
hidden one — never aria-label alone if a real label can be shown.
A visible label helps everyone, including sighted cognitive users.
visually-hidden-label.html
<label for="site-search" class="visually-hidden">Search the site</label>
<input type="search" id="site-search" name="q" placeholder="Search…">
How to fix
Add a <label> for every field and tie it to the input
with matching for and id values.
Keep the label visible. Use a visually-hidden label only when
the design truly can’t show text.
Use placeholder only for an example of the expected format,
never as the field’s name or its only instruction.
Verify in the accessibility tree that the field is announced with its label
and role, e.g. “Email, edit text”.
Error message not associated or announced
WCAG 2.2 · 3.3.1A3.3.3AA4.1.3AAEN 301 549
An error shown only as red text near a field doesn’t
reach a screen reader user: it isn’t part of the field’s name, and nothing tells
assistive technology that anything changed. The user submits, focus stays put, and
the page looks identical to them. Three things have to be true — the error text
must be associated with its field via aria-describedby, the
field must be marked aria-invalid="true", and the failure must be
announced, either by moving focus to an error summary with
role="alert" or by rendering errors into a live region.
Bad
The message is visually next to the field but not connected to it. It isn’t
announced on focus, isn’t announced on submit, and the field isn’t marked
invalid.
bad-error.html
<label for="pw">Password</label>
<input type="password" id="pw" name="pw">
<span class="error">Password is required</span>
Good
The field points at its error with aria-describedby and is
marked aria-invalid="true", so the message is read on focus. The
error text describes how to fix the problem (3.3.3), not just that something is
wrong.
good-error.html
<label for="pw">Password</label>
<input type="password" id="pw" name="pw"
aria-describedby="pw-error" aria-invalid="true">
<span class="error" id="pw-error">
Enter a password of at least 8 characters.
</span>
Code
On submit, render an error summary, move focus to it so it’s read, and link
each item to its field. Give the summary role="alert" (or
tabindex="-1" plus a script focus) so failure is announced without
relying on the user to go hunting (4.1.3).
error-summary.html
<div role="alert" tabindex="-1" id="error-summary">
<h2>There is a problem</h2>
<ul>
<li><a href="#pw">Enter a password of at least 8 characters.</a></li>
</ul>
</div>
<!-- After render: errorSummary.focus(); -->
How to fix
Connect each error to its field with aria-describedby pointing
at the error element’s id.
Set aria-invalid="true" on the field while it’s in error, and
remove it once the value is valid.
Write the message as a fix, not just a flag — say what to enter (3.3.3).
On submit, show an error summary, move focus to it (or give it
role="alert"), and link each entry to the field it describes.
Don’t rely on colour alone; pair red with an icon or text so 1.4.1 is met
too.
Related controls not grouped
WCAG 2.2 · 1.3.1A3.3.2AEN 301 549Section 508
A set of radio buttons or checkboxes answers a single
question — “Delivery speed?”, “Which notifications?”. Each control has its own
label, but the question that ties them together is usually just plain text sitting
above them, invisible to the accessibility tree. A screen reader user arriving on
the third radio hears “Express, radio button, 2 of 3” with no idea what is being
chosen. Wrapping the set in a <fieldset> with a
<legend> makes the group’s question part of each control’s
context.
Bad
The grouping question is a bare paragraph. The radios are only related
visually, so the relationship is lost to assistive technology (1.3.1).
The same pattern works for a checkbox group. If a native
fieldset can’t be used (for example with custom layout), recreate
it with role="group" and aria-labelledby pointing at
the group’s heading.
Wrap each set of related radios or checkboxes in a
<fieldset>.
Put the group’s question in a <legend> as the first child
of the fieldset.
Keep an individual <label> on every control as well — the
legend names the group, the label names the option.
If you can’t use a native fieldset, use role="group" with
aria-labelledby referencing the visible question.
Reserve fieldsets for genuinely related controls; don’t wrap every single
input, or the extra announcements become noise.
Missing autocomplete on personal-data fields
WCAG 2.2 · 1.3.5AAEN 301 549ADA Title II
Identify Input Purpose asks that fields collecting a
user’s own information declare what they hold, using the standard
autocomplete tokens. With the right token, the browser and assistive
tools can fill the field automatically and, increasingly, show a personalised icon
next to it. That removes the burden of remembering and re-typing personal data —
which matters most for people with cognitive, motor, or memory disabilities. The
tokens are a fixed vocabulary (such as name, email,
tel), not arbitrary strings.
Bad
These fields collect the user’s own contact details but declare no purpose,
so the browser can’t reliably autofill them (1.3.5).
Each field declares its purpose with the correct token. The
type still drives the right keyboard and validation; the
autocomplete token drives autofill and input-purpose support.
Tokens can be qualified for multi-part data, and you can scope them to a
contact type. Use autocomplete="off" only where autofill would be
wrong, such as a one-time code.
autocomplete-tokens.html
<input autocomplete="given-name"> <!-- first name -->
<input autocomplete="family-name"> <!-- last name -->
<input autocomplete="street-address">
<input autocomplete="postal-code">
<input autocomplete="work email"> <!-- scoped to work contact -->
<input autocomplete="one-time-code"> <!-- SMS code field -->
How to fix
Add an autocomplete attribute to every field that collects the
user’s own data — name, email, phone, address, and so on.
Use the exact tokens from the HTML autofill vocabulary
(name, email, tel,
given-name, postal-code…), not invented values.
Set the matching type as well so the right keyboard and
validation appear on mobile.
Only use autocomplete="off" where autofill is genuinely
inappropriate; never use it to defeat password managers.
Recap
Give every input a real, programmatic label — a <label for>
tied to the field’s id. A placeholder is not a label (1.3.1, 3.3.2,
4.1.2).
Wire each error to its field with aria-describedby, mark the field
aria-invalid="true", and announce errors through a focused summary or
a live region (3.3.1, 3.3.3, 4.1.3).
Group related radios and checkboxes in a <fieldset> with a
<legend> that asks the question (1.3.1, 3.3.2).
Add the correct autocomplete tokens to personal-data fields so the
browser can fill them (1.3.5).
The same fixes satisfy WCAG, EN 301 549, Section 508, and ADA
Title II at once — wire each field correctly and you meet them all.