Why forms fail accessibility so consistently
Most form accessibility failures share a common root: the form was designed visually, and the visual design looked good enough that no one asked whether it worked without sight, without a mouse, or without the context that visual proximity provides.
Sighted users reading a form can see that the field below the word "Email" is the email field. They can see that an asterisk means required because there is a legend somewhere on the page explaining it. They can see that the placeholder text in the empty field says "Enter your message here." A screen reader user, a keyboard-only user, or a user with a cognitive disability cannot rely on any of this visual inference. What they need is explicit, programmatic information — embedded in the HTML in a way that assistive technology can read.
This is not a niche concern. According to Statistics Canada, approximately 22% of Canadians aged 15 and over have a disability. Forms are the primary way that websites collect information and facilitate transactions. An inaccessible form is a closed door.
Labels: the foundation of every accessible form
Every form field must have a visible, programmatically associated label. This is WCAG Success Criterion 1.3.1 (Level A) and 2.4.6 (Level AA). It is also the most commonly failed form accessibility criterion.
The correct implementation uses a <label> element with a for attribute that matches the id of the input:
<label for="email">Email address</label>
<input type="email" id="email" name="email">
When this association is in place, clicking the label text moves focus to the input — a usability benefit for all users, and a larger click target for people with motor difficulties. Screen readers announce the label when the field receives focus. The association is clear, robust, and requires no JavaScript.
The failure modes are common and worth naming. Labels that are adjacent to fields but not programmatically associated — for example, a <p> or <span> before an input — are visible but not connected. A screen reader will read "Email address" and then "edit text" for the input, but will not know they belong together. Wrapping the label around the input (the implicit label pattern) works in most browsers and assistive technologies, but the explicit for/id pattern is more robust and universally supported.
Labels must be visible. WCAG allows for labels that are visually hidden (using CSS that preserves them for assistive technology) only in specific situations where the context makes the purpose completely clear — for example, a single search field within a search interface. For general forms, visible labels are required.
The placeholder problem
Placeholder text — the greyed-out hint text inside an empty field — is one of the most persistent form accessibility problems on the web. It looks like a label. It is not a label.
Placeholder text disappears the moment a user starts typing. For anyone who needs to check what a field is asking — someone with a short-term memory impairment, someone filling out a long form, someone who tabbed away and came back — the context is gone. They must clear the field to see the placeholder again, which means losing the content they entered.
Placeholder text also typically fails the contrast requirements of WCAG SC 1.4.3: the default grey-on-white colour of placeholder text in most browsers has a contrast ratio well below the 4.5:1 required for normal text. And because it disappears on input, screen readers do not consistently announce it when a field receives focus.
The fix is to use placeholder text only as a supplement — for example, showing an expected format ("e.g. 604-555-0100") — never as a replacement for a label. The label goes above the field, always visible, always there. The placeholder provides optional additional context.
Instructions and helper text
When a field has specific format requirements, those requirements need to be communicated before the user attempts to complete the field — not after they have made an error. Telling someone "Phone number must include area code" only in an error message means they had to fail first to learn the rule.
Instructions placed below a label and above the input are persistent and visible. They need to be programmatically associated with the input so screen readers announce them when the field receives focus. The standard approach uses aria-describedby:
<label for="phone">Phone number</label>
<p id="phone-hint" style="font-size:0.875rem;color:#555;">
Include your area code, e.g. 250-555-0100
</p>
<input type="tel" id="phone" name="phone" aria-describedby="phone-hint">
With this pattern, a screen reader announces: "Phone number. Include your area code, e.g. 250-555-0100. Edit text." The user has everything they need before they start typing.
Required fields and format requirements
Indicating which fields are required is a WCAG Level A requirement (SC 3.3.2). The most robust implementation combines a visual indicator with a programmatic one.
The HTML required attribute is both functional (browsers enforce it natively, preventing form submission if the field is empty) and semantic (screen readers announce that the field is required). An asterisk or other visual indicator must be explained — a note near the top of the form saying "Fields marked with * are required" satisfies this.
<label for="name">
Full name <span aria-hidden="true">*</span>
</label>
<input type="text" id="name" name="name" required aria-required="true">
The aria-hidden="true" on the asterisk prevents screen readers from announcing "asterisk" — which is meaningless without visual context — while the required and aria-required="true" attributes communicate the requirement programmatically.
Error messages that actually help
Error messages are where most forms completely fall apart. The failure pattern is almost always the same: an error occurs, a message appears somewhere on the page (often in red text near the submit button, or floating above the form), and the user has no idea which field caused the problem, what specifically went wrong, or how to fix it.
WCAG SC 3.3.1 (Level A) requires that if an input error is automatically detected, the item that is in error is identified and the error is described to the user in text. SC 3.3.3 (Level AA) adds that suggestions for how to correct the error must be provided when known and when doing so would not jeopardize security.
Accessible error messages have three properties. They are specific: "Email address is required" rather than "Please check your form." They are associated programmatically with the field that caused the error, using aria-describedby pointing to the error message element. And they trigger focus management — when validation fails, focus should move to either the first error or a summary at the top of the form listing all errors.
<label for="email">Email address</label>
<input type="email" id="email" name="email"
aria-describedby="email-error"
aria-invalid="true">
<p id="email-error" role="alert" style="color:#c00;">
Enter a valid email address, e.g. name@example.com
</p>
The aria-invalid="true" attribute signals to screen readers that the field is in an error state. The role="alert" on the error message causes it to be announced immediately by screen readers when it appears, without requiring the user to navigate to it. Together, these ensure that users who cannot see the red text are still told, clearly and promptly, what went wrong.
Avoid indicating errors using colour alone — a red border on an input field conveys nothing to a user who cannot distinguish red from grey. The error must also be communicated in text.
Keyboard and focus behaviour
Form fields need to be reachable by keyboard in a logical order, and focus indicators need to be visible (see the keyboard accessibility article for full detail on this). But forms have some specific keyboard behaviour requirements worth addressing separately.
The Tab key should move through form fields in the order a sighted user would fill them out — top to bottom, left to right. If your form is laid out with CSS Grid or Flexbox and the visual order differs from the source order, fix the source order rather than using tabindex to patch it.
Submit buttons should be reachable by Tab and activatable with Enter or Space. If your submit button is a styled <div> or <a> element rather than a <button> or <input type="submit">, keyboard users may not be able to activate it. Use <button type="submit"> — it is keyboard accessible, announced correctly by screen readers, and activated by both Enter and Space.
After a form is submitted successfully, focus management matters. If the page reloads, focus returns to the top — which is fine, as long as the success message is near the top or is announced by a live region. If submission is handled via JavaScript without a page reload, focus must be explicitly moved to the success message or confirmation area.
Autocomplete attributes
WCAG SC 1.3.5 (Level AA) requires that form fields collecting information about the user — name, email, phone, address, payment details — use appropriate autocomplete attribute values. This allows browsers and password managers to autofill the fields, which is especially important for users with motor disabilities (who find repetitive typing difficult), cognitive disabilities (who may struggle to remember information), and anyone using a mobile device.
The correct values are standardized. autocomplete="name" for a full name field. autocomplete="email" for email. autocomplete="tel" for phone number. autocomplete="street-address" for street address. autocomplete="postal-code" for postal code. autocomplete="cc-number" for credit card number (on payment forms).
Adding these attributes takes ten minutes and meaningfully improves the experience for a significant portion of your users. It also reduces form abandonment — autofill reduces friction for everyone, not just users with disabilities.
Grouped fields: radio buttons, checkboxes, selects
Radio button groups and checkbox groups need special handling because they involve multiple inputs that collectively answer a single question. The grouping must be marked up using <fieldset> and <legend>, so that the question (the legend) is announced together with each individual option (the label).
<fieldset>
<legend>Which plan are you interested in?</legend>
<label>
<input type="radio" name="plan" value="essential"> Essential
</label>
<label>
<input type="radio" name="plan" value="care"> Care
</label>
<label>
<input type="radio" name="plan" value="priority"> Priority
</label>
</fieldset>
A screen reader will announce each option as "Essential, radio button, 1 of 3, Which plan are you interested in?" — the question is included because it is the fieldset legend. Without the fieldset and legend, screen readers announce only the label text, and the user has no idea what question the radio buttons are answering.
Native <select> elements are keyboard accessible by default — they can be opened with Space or Enter, navigated with arrow keys, and selected with Enter. Custom styled selects built from divs and spans are not keyboard accessible unless JavaScript keyboard handling has been carefully implemented. Where possible, use native <select> and style it with CSS rather than replacing it with a custom component.
Success states and confirmation
Accessible form design does not end when the user submits. What happens next needs to communicate clearly that the action was successful — or that it was not.
A success message should be visible, clearly worded ("Your message has been sent. We will respond within one business day."), and programmatically announced to screen readers. Using role="alert" or aria-live="polite" on the confirmation container causes screen readers to announce it when it appears.
If the page reloads to a confirmation page, the page title should confirm the success ("Message sent — Company Name") so that users who cannot see the page content know immediately from the browser tab that the action succeeded.
Never clear a form after a submission error without warning. If a user's session times out or their submission fails and the form is cleared, they have lost all their work. Preserve field values after validation failures so users only need to correct the specific error, not re-enter everything.
Testing your forms
Automated tools catch some form accessibility failures — WAVE will flag missing labels and fields with no accessible name. But many of the most impactful form failures require manual testing.
Test your form by keyboard only: Tab through every field, check that the order makes sense, verify you can submit with Enter, verify error messages appear and are readable. Check that you can understand which field each error refers to without looking at the form layout.
Test with a screen reader. On Mac, VoiceOver is built in (Command+F5 to activate). On Windows, NVDA is free and widely used. Navigate your form in browse mode and then form mode. Can you tell what each field is asking? Are required fields announced? When you trigger an error, does the screen reader tell you what went wrong?
Zoom your browser to 200% and check that the form is still usable — fields do not overlap, labels remain above their inputs, error messages are still visible alongside their fields.
Forms are high-stakes — they are where your users take action. Getting them right for everyone is not a significant technical burden. A contact form with proper labels, visible instructions, clear error messages, and keyboard support takes roughly the same amount of time to build as one without. The difference is knowing what to build. Now you do.