Accessible forms come down to four things: every control has a real label, related fields are grouped, instructions arrive before the user needs them, and errors are described in text the screen reader can read. Get those right and most of the work is done. The <label> element, tied to a control with matching for and id, is the foundation (W3C). Group things like a set of radio buttons with <fieldset> and <legend> so their shared question is announced (W3C). Put required-field and format notes where the user meets them, and attach them with aria-describedby when they sit apart from the label (W3C). When validation fails, say what went wrong and how to fix it, in words, not colour alone (W3C). Each of these maps to a specific WCAG success criterion, so this guide walks through them in the order you build a form, with the markup for each.
What makes a form accessible?
A form is accessible when someone using a keyboard, a screen reader, or voice control can find each field, understand what it wants, fill it in, and recover from a mistake without seeing the screen. That splits across a handful of WCAG 2.2 criteria. Labels and instructions must be present (3.3.2 Labels or Instructions, Level A). They must be programmatically associated with their controls (1.3.1 Info and Relationships, Level A). Each control must expose a correct name and role (4.1.2 Name, Role, Value, Level A). Errors must be identified in text (3.3.1 Error Identification, Level A) and, where the fix is knowable, suggested (3.3.3 Error Suggestion, Level AA).
The split between 3.3.2 and 1.3.1 trips people up. 3.3.2 asks whether a label exists at all; 1.3.1 asks whether it is wired to the field (W3C). A form can pass one and fail the other. A grey placeholder that reads "Email" looks like a label but is attached to nothing, so it fails 1.3.1 even though a sighted user sees text. That gap is where a lot of forms quietly break.
How do I label every form control?
Use a <label> and connect it explicitly. The for attribute on the label has to match the id on the control, exactly (W3C):
<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email">
That one association does a lot. A screen reader announces "Email address, edit text" when focus lands on the input, and clicking the label puts the cursor in the field, which helps anyone with a tremor hit a small checkbox. You can also wrap the input inside the label (implicit association), but explicit for/id has better assistive-technology support, so I default to it.
Placeholder text is not a label. The W3C is direct about this: assistive technologies do not treat a placeholder as a label, and it disappears the moment the user starts typing, so they lose the instruction just when they might reread it (W3C). If your design has no room for a visible label, keep the <label> in the markup and hide it with a clipping technique (a 1-by-1px clip), not with display:none, which removes it from the accessibility tree (W3C). When no visible text exists at all, such as a search field next to a magnifier button, aria-label="Search" on the input gives it a name. Reach for ARIA only after a real <label> is off the table, because a native label is what browsers and screen readers handle most predictably.
When should I group fields with fieldset and legend?
When several controls answer one question. A set of radio buttons for a shipping speed, a pair of checkboxes for consent, a billing address split across lines: on their own each input has a label, but the question tying them together lives nowhere until you add a <fieldset> with a <legend> (W3C).
<fieldset>
<legend>Delivery speed</legend>
<label><input type="radio" name="speed" value="standard"> Standard (3–5 days)</label>
<label><input type="radio" name="speed" value="express"> Express (next day)</label>
</fieldset>
Now a screen reader announces "Delivery speed, Standard, radio button, 1 of 2." Without the legend the user hears "Standard, radio button" with no idea what they are choosing. You do not need a fieldset around every input, and wrapping single unrelated fields in them adds noise. Use it where a group shares one prompt.
How do I write good form instructions?
Say what a field needs before the user commits to an answer, and put the note where they will find it. Overall rules, such as which fields are required or a date format that applies to the whole form, belong before the <form> element, because screen readers switch into a "forms mode" inside the form and can skip text that is not part of a control (W3C).
For a note about one field, the simplest option is to fold it into the label: <label for="expiry">Expiry date (MM/YYYY)</label>. When the instruction is longer or sits visually apart, associate it with aria-describedby so it is announced after the label:
<label for="pw">Password</label>
<input type="password" id="pw" aria-describedby="pw-help">
<p id="pw-help">At least 12 characters, including a number.</p>
Mark required fields in a way that does not depend on sight. An asterisk alone leaves a screen reader user to guess, so pair it with the required attribute (which browsers expose to assistive tech) and state "required" in text somewhere they will hear it. This is also where WCAG 2.2's newer criteria matter: 3.3.7 Redundant Entry (Level A) says do not force people to re-enter information they already gave you earlier in the same process, and 3.3.8 Accessible Authentication (Level AA) says a login must not require a cognitive test like solving a puzzle or retyping a code from memory. Both arrived with WCAG 2.2, and both live in forms.
How should a form handle errors?
An error has to be findable, readable, and fixable. WCAG 3.3.1 Error Identification (Level A) requires that when the form detects an input error, the item in error is identified and the problem described in text (W3C). WCAG 3.3.3 Error Suggestion (Level AA) goes further: if you know how to fix it, tell the user (W3C).
In practice:
- Describe the fix, not just the failure. "Enter a date in the past" beats "Invalid date." "Invalid" tells the user nothing they can act on.
- Put the message next to the field and tie it in with
aria-describedby, so focus into the field announces both the label and the error. - Do not signal errors with colour alone. A red border with no text fails users who cannot distinguish it, which is WCAG 1.4.1 Use of Color (Level A). Add an icon and text, not just a hue.
- Let the user reach the errors. Move focus to the first error, or list the errors at the top with in-page links to each field.
HTML5 gives you a head start. type="email", type="url", and a pattern attribute catch common mistakes in the browser, but the W3C is clear that client-side checks are a convenience, not the guard: validate on the server too (W3C).
Should I use autocomplete and the right input types?
Yes, and it is a small change with real payoff. Setting autocomplete="email", autocomplete="name", autocomplete="tel", and the like lets the browser fill fields and, more importantly, lets assistive tools understand a field's purpose. That is WCAG 1.3.5 Identify Input Purpose (Level AA), which asks that the purpose of common input fields be programmatically determinable so tools can adapt them, for example by adding familiar icons for someone with a cognitive disability (W3C). Pairing it with the right type (email, tel, number) also gives mobile users the correct on-screen keyboard, so the phone-number field shows digits instead of letters.
What can a scanner catch, and what needs a human?
A scanner is good at the mechanical half: inputs with no associated label, a for that points at no id, a missing fieldset around a radio group, a placeholder standing in for a label. Those are exactly the failures a machine can see in the DOM, and they are worth catching in bulk. Across more than 2,000 audits, Deque found automated testing caught about 57% of accessibility issues, with narrower estimates around 30% by the share of success criteria a tool can even evaluate (Deque).
The other half is judgement. Whether your error message actually helps someone fix the problem, whether the tab order matches the visual order, whether an instruction is clear to a stressed user on a slow connection: no scanner reads for sense. This is the honest line overlay widgets cross when they claim a script makes a form accessible. That kind of claim is what cost the vendor accessiBe a $1 million settlement with the US Federal Trade Commission for misrepresenting what its product could do (FTC). Use automation to find the broken labels; test the flow with a keyboard and a screen reader to know it works. These form criteria are also written into EN 301 549, the standard behind the European Accessibility Act, so a form that fails 3.3.2 or 1.3.1 is a legal gap in the EU, not only a usability one.
Frequently asked questions
What is the most common accessible form mistake?
Using placeholder text as the only label. It vanishes when typing starts and is not exposed to assistive technology as a label, which fails WCAG 1.3.1. Keep a real <label> on every control, visible or clipped, and use the placeholder only for an example value.
Do I need a label on every single field?
Every control that takes input needs an accessible name, yes. Usually that is a <label> with matching for/id. A field with visible text next to it but no association still fails, because the connection is what a screen reader reads.
When do I use fieldset and legend?
When several controls answer one shared question, such as a group of radio buttons or related checkboxes. The <legend> supplies the question the individual labels cannot. A lone text input does not need a fieldset.
Is placeholder text enough for instructions?
No. Placeholders disappear once the user types and are unreliable for assistive technology (W3C). Put instructions in the label or associate them with aria-describedby so they stay available.
Which WCAG criteria apply to forms?
The core set is 3.3.2 Labels or Instructions, 1.3.1 Info and Relationships, 4.1.2 Name, Role, Value, 3.3.1 Error Identification, and 3.3.3 Error Suggestion, plus 1.3.5 Identify Input Purpose and 1.4.1 Use of Color. WCAG 2.2 adds 3.3.7 Redundant Entry and 3.3.8 Accessible Authentication.
Find the form issues on your pages
The quickest start is to see which inputs on your live pages have no associated label, a broken for/id, or a missing group. Run a free scan to surface the machine-detectable form failures across your key pages, then work the WCAG checklist for the judgement calls, like whether an error message really tells someone how to recover. Automation finds the broken wiring; a keyboard and a screen reader confirm the form actually works.
Pavel Charkasau, founder, wcagc.com. Last updated 4 August 2026.
Sources
- Forms Tutorial, W3C WAI — labeling, grouping, instructions, validation, and custom controls. Accessed 4 August 2026.
- Labeling Controls, W3C WAI Forms Tutorial — the
for/idassociation, hidden labels, and why placeholders are not labels. Accessed 4 August 2026. - Form Instructions, W3C WAI Forms Tutorial — required fields, formats,
aria-describedby, and placeholder limits. Accessed 4 August 2026. - Validating Input, W3C WAI Forms Tutorial — HTML5 validation, server-side validation, and error prevention. Accessed 4 August 2026.
- Understanding SC 3.3.2: Labels or Instructions, W3C — the Level A requirement and its split from 1.3.1. Accessed 4 August 2026.
- Understanding SC 3.3.1: Error Identification, W3C — identifying and describing input errors in text. Accessed 4 August 2026.
- Understanding SC 3.3.3: Error Suggestion, W3C — suggesting a correction when it is known. Accessed 4 August 2026.
- Understanding SC 1.3.5: Identify Input Purpose, W3C — autocomplete and programmatically determinable field purpose. Accessed 4 August 2026.
- Automated testing identifies 57% of accessibility issues, Deque — automated coverage figures. Accessed 4 August 2026.
- FTC approves final order requiring accessiBe to pay $1 million, Federal Trade Commission — deceptive accessibility-compliance claims. Accessed 4 August 2026.