← Back to Insights
Accessible forms

Accessible Forms: The Developer's Fix-First Guide to WCAG 2.2 Compliance

Generated image

Forms are where accessibility goes wrong more than anywhere else on the web. They are also where it matters most: a checkout that a screen reader user cannot complete is not a minor inconvenience - it is a lost sale, a legal exposure, and a barrier that affects real people trying to buy things, open accounts, or access services.

The numbers back this up. The WebAIM Million 2026 report found an average of 56.1 detectable WCAG errors per home page - a 10.1% increase year-on-year. Missing form input labels have been in the top-six failure list every single year since WebAIM began the study, and the number of pages with missing form input labels has increased slightly over the last few years, even as some other error types have improved.

For teams building in EAA scope - checkout, login, account creation, contact, applications - this is the fix-first guide. It covers every WCAG success criterion that directly touches forms, with concrete HTML patterns and the reasoning behind each one. The baseline is WCAG 2.1 AA (the EAA's technical floor via EN 301 549), with the WCAG 2.2 additions called out explicitly.


1. Labels: the single biggest form failure

Relevant SCs: 1.3.1 Info and Relationships (A), 4.1.2 Name, Role, Value (A), 3.3.2 Labels or Instructions (A)

Every input needs a programmatically associated label. That means a <label> element whose for attribute matches the input's id. Without it, a screen reader user hears "edit text" with no context.

❌ Bad - placeholder as label:

<input type="email" placeholder="Email address">

Placeholder text disappears the moment a user starts typing. Browsers render placeholder text at roughly 60% opacity of the input text colour, which fails WCAG 1.4.3 contrast in almost every default implementation. It is not a label under any WCAG criterion.

✅ Good - explicit label association:

<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email">

Where a visible label is genuinely not possible (icon-only search bars, for example), use aria-label or aria-labelledby - but treat these as last resorts. Visible labels are always preferable because they help all users, including those with cognitive disabilities.

warning Warning

aria-label does not replace a visible label. It satisfies 4.1.2 for assistive technology but leaves sighted users with cognitive disabilities, or anyone who relies on voice control software (which matches spoken words to visible text), without a usable label. Always prefer a visible <label> element.


2. Required fields and upfront instructions

Relevant SC: 3.3.2 Labels or Instructions (A)

Tell users what is required before they submit, not after. The most common failure: a red asterisk with no legend explaining what it means.

✅ Correct pattern:

<p>Fields marked with <abbr title="required">*</abbr> are required.</p>

<label for="name">
  Full name <span aria-hidden="true">*</span>
  <span class="sr-only">(required)</span>
</label>
<input type="text" id="name" name="name" required aria-required="true"
       autocomplete="name">

Key points:

  • The legend ("Fields marked with * are required") must appear before the first required field in DOM order.
  • Required field indicators that use colour alone - a red asterisk without explanation - fail WCAG 1.4.1, which prohibits colour as the only means of conveying information.
  • Use both the HTML required attribute (triggers native browser validation) and aria-required="true" for maximum assistive technology compatibility.
  • Provide format instructions upfront: "Date (DD/MM/YYYY)" is better than an error message after the fact.

3. Grouping related controls

Relevant SC: 1.3.1 Info and Relationships (A)

Radio buttons and checkboxes that belong to a question must be wrapped in a <fieldset> with a <legend>. Without this, a screen reader user hears each option in isolation and has no idea what question it answers.

❌ Bad:

<p>Preferred contact method</p>
<input type="radio" id="r-email" name="contact"> <label for="r-email">Email</label>
<input type="radio" id="r-phone" name="contact"> <label for="r-phone">Phone</label>

✅ Good:

<fieldset>
  <legend>Preferred contact method</legend>
  <input type="radio" id="r-email" name="contact" value="email">
  <label for="r-email">Email</label>
  <input type="radio" id="r-phone" name="contact" value="phone">
  <label for="r-phone">Phone</label>
</fieldset>

The <legend> is announced before each option, so the user hears "Preferred contact method - Email" rather than just "Email". Use <fieldset>/<legend> for any group of related inputs: delivery options, payment method selectors, consent checkboxes.


4. Autocomplete and input purpose

Relevant SC: 1.3.5 Identify Input Purpose (AA)

WCAG 1.3.5 requires that the purpose of input fields collecting user information be programmatically identifiable using standardised HTML attributes - specifically, the autocomplete attribute with valid token values from the HTML specification. This lets browsers, password managers, and assistive technologies autofill forms accurately.

The benefit is concrete: Google reports that proper autocomplete implementation can reduce checkout time by up to 30%. For users with motor disabilities or cognitive impairments who struggle with repetitive data entry, it is not a convenience - it is an access requirement.

✅ Correct token usage for a checkout form:

<input type="text"  id="name"    autocomplete="name">
<input type="email" id="email"   autocomplete="email">
<input type="tel"   id="phone"   autocomplete="tel">
<input type="text"  id="addr1"   autocomplete="street-address">
<input type="text"  id="city"    autocomplete="address-level2">
<input type="text"  id="postcode" autocomplete="postal-code">
<input type="text"  id="cc-num"  autocomplete="cc-number">
<input type="text"  id="cc-exp"  autocomplete="cc-exp">

Common mistakes to avoid:

  • autocomplete="fullname" is invalid - use name or split into given-name / family-name.
  • autocomplete="phone-number" is invalid - use tel.
  • Do not use autocomplete="off" on personal-data fields. Browsers largely ignore it now, and it breaks password manager autofill.
  • The criterion only applies to fields collecting the user's own information. A "gift recipient name" field does not need an autocomplete token.

5. Error identification, suggestion, and summaries

Relevant SCs: 3.3.1 Error Identification (A), 3.3.3 Error Suggestion (AA)

This is where most forms fail in practice. The pattern that breaks everything: submit a form, JavaScript updates the DOM with red-bordered fields, focus stays on the submit button, and the screen reader user has no idea anything happened.

A user submitting a checkout form where focus stays on the submit button and the screen reader never reaches the new errors is a failed recovery path under WCAG and a direct abandonment risk.

What WCAG requires

3.3.1 Error Identification: Identify the field in error in text, not colour alone. "This field is required" is not enough - name the field: "Email address is required."

3.3.3 Error Suggestion: Tell users how to fix it. "Enter a valid email address (example: name@domain.com)" is compliant. "Invalid input" is not.

The correct HTML pattern

<label for="email">Email address</label>
<input type="email" id="email" name="email"
       aria-describedby="email-error"
       aria-invalid="true"
       autocomplete="email">
<span id="email-error" role="alert">
  Enter a valid email address (example: name@domain.com)
</span>

Key attributes:

  • aria-invalid="true" - set on the input when it fails validation; remove or set to false when corrected.
  • aria-describedby - links the error message to the input so screen readers announce it when the field receives focus.
  • role="alert" - causes the message to be announced immediately when injected into the DOM.

Error summaries and focus management

For long forms or multi-error submissions, inject an error summary at the top of the form and move focus to it on submit:

<div id="error-summary" tabindex="-1" role="alert">
  <h2>There are 2 errors in this form</h2>
  <ul>
    <li><a href="#email">Email address: enter a valid email address</a></li>
    <li><a href="#postcode">Postcode: enter a valid UK postcode</a></li>
  </ul>
</div>
// After failed submit, inject summary then:
document.getElementById('error-summary').focus();

In React, Vue, Angular, or Svelte, state changes can update the UI without moving focus or creating an announcement that assistive technology will pick up - teams need to design that behaviour deliberately. A tabindex="-1" on the summary container makes it programmatically focusable without adding it to the tab order.


6. Redundant Entry - the WCAG 2.2 addition that checkout teams miss

Relevant SC: 3.3.7 Redundant Entry (Level A, new in WCAG 2.2)

WCAG 3.3.7 Redundant Entry is a Level A success criterion introduced in WCAG 2.2 that requires information previously entered by the user in the same process not to be required again, except where re-entry is essential for security or validation purposes.

The canonical checkout example: a user fills in a 10-field shipping address, then reaches the billing step and finds an empty form. No "same as shipping" option. They must retype everything. For users with motor disabilities, this is not just frustrating - it is a barrier. For users with cognitive impairments, it increases error rates and abandonment.

Three compliant approaches:

  1. Pre-fill automatically - carry the shipping address into the billing fields and let the user edit if needed.
  2. "Same as shipping" checkbox - the W3C's own example; ticking it populates the billing fields.
  3. Selection from previously entered data - a dropdown showing the address already entered.
<fieldset>
  <legend>Billing address</legend>
  <label>
    <input type="checkbox" id="same-as-shipping">
    Same as shipping address
  </label>
  <!-- Billing fields pre-populated via JS when checkbox is ticked -->
  <label for="billing-addr1">Address line 1</label>
  <input type="text" id="billing-addr1" autocomplete="billing address-line1">
</fieldset>

Exceptions - re-entry is permitted when:

  • It is a security measure (password confirmation on account creation).
  • The previously entered data is no longer valid.
  • The user explicitly chooses to re-enter (unchecking the "same as" option).

Note: the criterion applies within a single session/process. If a user abandons and returns later, you are not required to persist their data.


7. Focus visibility and target size

Relevant SCs: 2.4.7 Focus Visible (AA, WCAG 2.1), 2.5.8 Target Size Minimum (AA, new in WCAG 2.2)

Focus visibility

Every interactive element must have a visible focus indicator. The most common failure: outline: none applied globally in a CSS reset, with no custom replacement.

/* ❌ Never do this without a replacement */
* { outline: none; }

/* ✅ Custom focus indicator that meets 2.4.7 */
:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
}

WCAG 2.2 added SC 2.4.11 Focus Not Obscured (AA), which requires the focused component not to be entirely hidden by sticky headers or overlays. Check that your cookie banners and fixed navigation bars do not cover focused form fields.

Target size

WCAG 2.5.8 Target Size (Minimum), new in WCAG 2.2, requires touch targets of at least 24×24 CSS pixels for all interactive elements. That is the legal floor - not a comfortable one. Users with motor impairments experience error rates up to 75% higher on small targets. Apple's Human Interface Guidelines recommend 44×44 points; most mature design systems target 44-48 px minimum for form controls.

Apply this to: checkboxes, radio buttons, toggle switches, inline "remove" links, and any icon-only button inside a form.


8. Keyboard operability

Relevant SC: 2.1.1 Keyboard (A)

Every form interaction must be completable by keyboard alone. Common failures in forms:

  • Custom select dropdowns built with <div> elements that only respond to mouse clicks. Use native <select> or implement full ARIA combobox patterns with keyboard event handlers.
  • Date pickers that trap focus or require mouse interaction to select a date. Always allow direct text entry as an alternative.
  • Multi-step forms where focus is not moved to the new step content on transition. When a user submits Step 1 and Step 2 loads, focus must move to a logical starting point in the new content - if focus stays on the submit button (which may no longer exist), screen reader users have no signal that the page state has changed.
  • Modal forms (account creation overlays, checkout dialogs) where focus is not trapped inside the modal. Focus should be trapped inside an open modal - this is the one intentional exception to the "no keyboard trap" rule.

Test every form with Tab, Shift+Tab, Enter, Space, and arrow keys before shipping.


Prioritised fix checklist

Use this to triage your highest-impact work first. Items are ordered by failure frequency and user impact.


What to do next

The fixes above are not aspirational - they are the minimum required under WCAG 2.1 AA (the EAA's technical floor via EN 301 549) plus the WCAG 2.2 additions that apply specifically to forms. The good news: most of them are pure HTML. They do not require a design system overhaul or a new library. They require discipline in markup.

Start with your highest-stakes journeys: checkout, account creation, login. Run an automated scan to surface the label, contrast, and autocomplete failures that tools can catch reliably. Then test manually with a keyboard and a screen reader - that is the only way to verify focus management, error announcement, and multi-step transitions.

help_outlineIs placeholder text ever acceptable as a label?expand_more

No. Placeholder text disappears when a user starts typing, leaving them with no reminder of what the field expects. It also typically fails WCAG 1.4.3 contrast requirements. Always use a visible <label> element. You can use placeholder text in addition to a label to show an example value (e.g., placeholder="name@example.com"), but never as a substitute.

help_outlineDoes WCAG 3.3.7 Redundant Entry apply to password confirmation fields?expand_more

No. Re-entering a password during account creation is explicitly listed as an exception because the original string is not displayed and cannot be validated by the author. The criterion also does not apply between separate sessions — only within the same continuous process.

help_outlineWhat is the difference between aria-invalid and the HTML required attribute?expand_more

required (or aria-required="true") signals that a field must be filled before submission. aria-invalid="true" signals that the current value fails validation — it should only be set after the user has attempted to submit or leave the field. Setting aria-invalid on load before any interaction is incorrect and confusing.

help_outlineDo I need fieldset/legend for a single checkbox (e.g., a terms and conditions checkbox)?expand_more

No. A single checkbox with a properly associated <label> is sufficient. <fieldset> and <legend> are needed when two or more related controls share a common question or context that is not conveyed by the individual labels alone.

help_outlineWhich WCAG version does the EAA actually require?expand_more

The EAA's harmonised standard, EN 301 549 v3.2.1, currently incorporates WCAG 2.1 AA as the technical floor. An update referencing WCAG 2.2 is expected but not yet published. Building to WCAG 2.2 AA is the recommended approach — it is a superset of 2.1, so meeting 2.2 automatically satisfies the current legal requirement.