Form Accessibility Guide: Labels, Errors, and WCAG Compliance
Form accessibility failures block users with disabilities from completing critical tasks—account creation, checkout, contact submissions, and any data entry interaction. Missing form labels are the third most common accessibility error, appearing on 45% of home pages according to the WebAIM Million. For e-commerce sites, inaccessible forms mean lost sales and legal liability.
WCAG requires form inputs to have associated labels (1.3.1), instructions to be provided (3.3.2), errors to be identified (3.3.1), and suggestions to be offered (3.3.3). Meeting these requirements ensures all users can complete forms successfully.
Q: What makes a form WCAG compliant?
A: WCAG-compliant forms have programmatically associated labels for every input, clear instructions where needed, error messages that identify the problem and suggest fixes, and accessible validation that doesn't rely solely on color. Users must be able to complete the form using keyboard alone.
Form Labels Fundamentals
Why Labels Matter
Screen readers announce form field labels. Without them, users hear "edit text" with no context—they don't know what information to enter.
Screen reader experience without label:
"Edit text" (User has no idea what to enter)
-
Screen reader experience with label:
"Email address, edit text" (User knows exactly what's needed)
-
Proper Label Association
Explicit association with `for` attribute (preferred):
<label for="email">Email address</label>
<input type="email" id="email" name="email">Implicit association by wrapping:
<label>
Email address
<input type="email" name="email">
</label>Using `aria-labelledby`:
<span id="email-label">Email address</span>
<input type="email" aria-labelledby="email-label">Using `aria-label` (for visually hidden labels):
<input type="search" aria-label="Search products">Label Best Practices
Always visible: Labels should be visible, not just placeholders. Placeholders disappear when users type, leaving no context.
Above or beside input: Position labels consistently—typically above or to the left of inputs.
Concise but clear: "Email" is better than "Please enter your email address here."
Match field purpose: The label should accurately describe what input is expected.
Placeholder vs Label
Placeholders are NOT labels:
<!-- Wrong: Placeholder alone doesn't provide accessible label -->
<input type="email" placeholder="Email address">
<!-- Right: Label plus optional placeholder -->
<label for="email">Email address</label>
<input type="email" id="email" placeholder="john@example.com">Placeholder problems:
- Disappears when typing
- Often low contrast (fails 4.5:1 requirement)
- Not announced consistently by screen readers
- Users may mistake placeholder for entered value
Required Fields
Indicating Required Status
Visual indication:
- Asterisk (*) with explanation
- "Required" text
- Or indicate optional fields instead
Programmatic indication:
<!-- Using required attribute -->
<label for="email">Email address *</label>
<input type="email" id="email" required>
<!-- Using aria-required for custom validation -->
<label for="email">Email address *</label>
<input type="email" id="email" aria-required="true">Legend for asterisks:
<p class="required-legend">* Required fields</p>
<label for="name">Full name *</label>
<input type="text" id="name" required>Screen Reader Announcements
When required attribute is present, screen readers announce:
"Email address, required, edit text"
-
This tells users they must complete the field before submitting.
Error Handling
WCAG Error Requirements
3.3.1 Error Identification (Level A): If an error is detected, the item in error must be identified and described in text.
3.3.3 Error Suggestion (Level AA): If an error is detected and suggestions are known, provide them (unless it would compromise security).
Error Message Best Practices
Be specific: "Please enter a valid email address" not "Invalid input"
Be helpful: Explain what's wrong AND how to fix it
Associate with field: Error messages must be programmatically linked to their fields
<!-- Error message associated with field -->
<label for="email">Email address</label>
<input type="email"
id="email"
aria-describedby="email-error"
aria-invalid="true">
<span id="email-error" class="error">
Please enter a valid email address (example: name@domain.com)
</span>Announcing Errors to Screen Readers
Option 1: Live region for error summary
<div role="alert" aria-live="assertive" id="error-summary">
<!-- Populated when errors occur -->
</div>
<script>
function showErrors(errors) {
const summary = document.getElementById('error-summary');
summary.textContent = `${errors.length} errors found. Please correct the highlighted fields.`;
}
</script>Option 2: Focus management on submit
function handleSubmit(event) {
const errors = validateForm();
if (errors.length > 0) {
event.preventDefault();
// Move focus to first error
const firstErrorField = document.getElementById(errors[0].fieldId);
firstErrorField.focus();
// Error message already associated via aria-describedby
}
}Error Display Patterns
Inline errors (recommended):
<div class="form-field error">
<label for="password">Password</label>
<input type="password"
id="password"
aria-invalid="true"
aria-describedby="password-error">
<span id="password-error" class="error-message">
Password must be at least 8 characters
</span>
</div>Error summary plus inline:
<div role="alert" class="error-summary">
<h2>Please fix the following errors:</h2>
<ul>
<li><a href="#email">Email address is required</a></li>
<li><a href="#password">Password is too short</a></li>
</ul>
</div>
<!-- Individual fields have inline errors too -->Don't Rely on Color Alone
Wrong: Only red border indicates error
.error input {
border-color: red; /* Color-blind users may miss this */
}Right: Multiple indicators
<div class="form-field error">
<label for="email">Email address</label>
<input type="email"
id="email"
aria-invalid="true"
aria-describedby="email-error">
<!-- Icon + text error message provides non-color indication -->
<span id="email-error" class="error-message">
âš Please enter a valid email address
</span>
</div>Form Instructions
When Instructions Are Needed
Provide instructions when:
- Format is required (dates, phone numbers)
- Specific constraints apply (character limits, allowed characters)
- Field purpose isn't obvious from label alone
Implementing Instructions
Using `aria-describedby`:
<label for="phone">Phone number</label>
<input type="tel"
id="phone"
aria-describedby="phone-instructions">
<span id="phone-instructions" class="instructions">
Format: (555) 555-5555
</span>In-label instructions:
<label for="username">
Username
<span class="instructions">(letters and numbers only)</span>
</label>
<input type="text" id="username">Character counters:
<label for="bio">Bio</label>
<textarea id="bio"
aria-describedby="bio-count"
maxlength="200"></textarea>
<span id="bio-count" aria-live="polite">
<span class="count">0</span>/200 characters
</span>Input Types and Autocomplete
Appropriate Input Types
Using correct input types enables:
- Appropriate mobile keyboards
- Built-in browser validation
- Autocomplete functionality
<input type="email"> <!-- Email keyboard, @ button -->
<input type="tel"> <!-- Phone keypad -->
<input type="number"> <!-- Numeric keypad -->
<input type="url"> <!-- URL keyboard -->
<input type="date"> <!-- Date picker -->
<input type="search"> <!-- Search functionality -->Autocomplete Attributes
WCAG 1.3.5 (Level AA) requires autocomplete for user data:
<input type="text" autocomplete="name">
<input type="email" autocomplete="email">
<input type="tel" autocomplete="tel">
<input type="text" autocomplete="street-address">
<input type="text" autocomplete="postal-code">
<input type="text" autocomplete="cc-number"> <!-- Credit card -->
<input type="text" autocomplete="cc-exp"> <!-- Expiration -->Benefits:
- Users can autofill from saved data
- Reduces typing for motor-impaired users
- Improves checkout conversion
- Required for WCAG 2.1 AA compliance
Complex Form Patterns
Fieldsets and Legends
Group related fields using <fieldset> and <legend>:
<fieldset>
<legend>Shipping Address</legend>
<label for="ship-street">Street address</label>
<input type="text" id="ship-street" autocomplete="shipping street-address">
<label for="ship-city">City</label>
<input type="text" id="ship-city" autocomplete="shipping address-level2">
<label for="ship-zip">ZIP code</label>
<input type="text" id="ship-zip" autocomplete="shipping postal-code">
</fieldset>
<fieldset>
<legend>Billing Address</legend>
<!-- Similar fields with billing autocomplete -->
</fieldset>Radio Button Groups
<fieldset>
<legend>Shipping speed</legend>
<input type="radio" id="standard" name="shipping" value="standard">
<label for="standard">Standard (5-7 days) - Free</label>
<input type="radio" id="express" name="shipping" value="express">
<label for="express">Express (2-3 days) - $9.99</label>
<input type="radio" id="overnight" name="shipping" value="overnight">
<label for="overnight">Overnight - $24.99</label>
</fieldset>Checkbox Groups
<fieldset>
<legend>Email preferences</legend>
<input type="checkbox" id="news" name="prefs" value="news">
<label for="news">Newsletter</label>
<input type="checkbox" id="promos" name="prefs" value="promos">
<label for="promos">Promotions and sales</label>
<input type="checkbox" id="updates" name="prefs" value="updates">
<label for="updates">Order updates</label>
</fieldset>Multi-Step Forms
For forms spanning multiple pages:
<!-- Progress indicator -->
<nav aria-label="Form progress">
<ol>
<li aria-current="step">Step 1: Shipping</li>
<li>Step 2: Payment</li>
<li>Step 3: Review</li>
</ol>
</nav>
<!-- Step content -->
<main>
<h1>Shipping Information</h1>
<!-- Form fields -->
</main>
<!-- Navigation -->
<button type="button">Back</button>
<button type="submit">Continue to Payment</button>E-Commerce Form Patterns
Checkout Forms
<form>
<fieldset>
<legend>Contact Information</legend>
<label for="email">Email address</label>
<input type="email" id="email" autocomplete="email" required>
<label for="phone">Phone number</label>
<input type="tel" id="phone" autocomplete="tel">
</fieldset>
<fieldset>
<legend>Shipping Address</legend>
<label for="name">Full name</label>
<input type="text" id="name" autocomplete="name" required>
<label for="address">Street address</label>
<input type="text" id="address"
autocomplete="street-address" required>
<label for="city">City</label>
<input type="text" id="city"
autocomplete="address-level2" required>
<label for="state">State</label>
<select id="state" autocomplete="address-level1" required>
<option value="">Select state</option>
<!-- State options -->
</select>
<label for="zip">ZIP code</label>
<input type="text" id="zip"
autocomplete="postal-code"
required
pattern="[0-9]{5}(-[0-9]{4})?"
aria-describedby="zip-format">
<span id="zip-format" class="hint">5 or 9 digit ZIP</span>
</fieldset>
<button type="submit">Continue to Payment</button>
</form>Payment Forms
<fieldset>
<legend>Payment Information</legend>
<label for="cc-name">Name on card</label>
<input type="text" id="cc-name" autocomplete="cc-name" required>
<label for="cc-number">Card number</label>
<input type="text" id="cc-number"
autocomplete="cc-number"
required
inputmode="numeric"
pattern="[0-9\s]{13,19}">
<div class="inline-fields">
<div>
<label for="cc-exp">Expiration (MM/YY)</label>
<input type="text" id="cc-exp"
autocomplete="cc-exp"
required
placeholder="MM/YY"
pattern="(0[1-9]|1[0-2])\/[0-9]{2}">
</div>
<div>
<label for="cc-csc">Security code</label>
<input type="text" id="cc-csc"
autocomplete="cc-csc"
required
inputmode="numeric"
maxlength="4"
aria-describedby="csc-help">
<span id="csc-help">3 or 4 digits on card</span>
</div>
</div>
</fieldset>Testing Form Accessibility
Automated Testing
TestParty identifies:
- Missing form labels
- Empty or duplicate labels
- Missing required field indicators
- Forms lacking error handling patterns
- Missing autocomplete attributes
Manual Testing Checklist
Keyboard testing:
- [ ] Can tab through all fields in logical order
- [ ] Can interact with all controls (selects, checkboxes, radios)
- [ ] Submit button is keyboard accessible
- [ ] No keyboard traps in form
Screen reader testing:
- [ ] All fields have announced labels
- [ ] Required status is announced
- [ ] Instructions are announced with fields
- [ ] Error messages are announced
- [ ] Form purpose is clear from announcements
Visual testing:
- [ ] Labels are visible (not placeholder-only)
- [ ] Required fields are indicated
- [ ] Errors use more than color
- [ ] Instructions are visible when needed
FAQ Section
Q: Can I use placeholder instead of label for cleaner design?
A: No. Placeholders are not accessible replacements for labels—they disappear when typing, have contrast issues, and aren't announced consistently. Use visible labels; supplement with placeholder if desired.
Q: How do I handle validation—inline or on submit?
A: Both patterns can be accessible. Inline validation provides immediate feedback but can be annoying for screen reader users if too aggressive. On-submit validation is simpler but requires good error summary. Consider gentle inline validation (validate on blur, not keystroke) combined with submit-time summary.
Q: Should I use HTML5 validation or custom?
A: HTML5 validation (required, pattern, type) provides baseline accessibility. Custom validation allows better error messages and consistent styling. Often the best approach is HTML5 attributes for programmatic state plus custom messages for user experience.
Q: How do I make CAPTCHA accessible?
A: CAPTCHAs are inherently problematic for accessibility. Provide audio alternatives. Consider alternatives: honeypot fields, time-based validation, or reCAPTCHA with accessible fallbacks. WCAG 2.2's Accessible Authentication criteria address this.
Q: What about date picker accessibility?
A: Custom date pickers need careful keyboard handling and ARIA. Native <input type="date"> has improved significantly but varies by browser. Always allow direct text entry as alternative to picker interface.
Key Takeaways
- Every form field needs a programmatic label. No exceptions. Placeholder text isn't a label.
- Required fields must be indicated visually and programmatically (using
requiredoraria-required). - Error messages must identify the problem and suggest how to fix it. Associate messages with fields using
aria-describedby. - Don't rely on color alone for any status indication. Use text, icons, and other non-color indicators.
- Use appropriate input types and autocomplete attributes for better user experience and WCAG compliance.
- Group related fields with
<fieldset>and<legend>for context.
Conclusion
Form accessibility determines whether users with disabilities can complete critical tasks on your site. For e-commerce, inaccessible checkout forms directly translate to lost sales and legal exposure.
The fundamentals are straightforward: label every field, indicate required status, provide clear error messages, don't rely on color alone. Getting these basics right covers the majority of form accessibility requirements.
TestParty identifies form accessibility issues and provides specific code fixes. For e-commerce sites, this means accessible checkout flows that work for all customers. For development teams, Bouncer catches form issues before deployment.
Ready to fix your forms? Get a free accessibility scan to identify form accessibility issues across your site.
Related Articles:
- E-Commerce Checkout Accessibility: Complete WCAG Guide
- Accessible Error Handling: Validation Patterns That Work
- Input Autocomplete: WCAG 1.3.5 Implementation Guide
Content disclosure: This article was produced using AI-assisted tools and reviewed by TestParty's team of accessibility specialists. As a company focused on source code remediation and continuous accessibility monitoring, we aim to share practical knowledge about WCAG and ADA compliance. That said, accessibility is complex and context-dependent. The information here is educational only—please work with qualified professionals for guidance specific to your organization's needs.
Stay informed
Accessibility insights delivered
straight to your inbox.


Automate the software work for accessibility compliance, end-to-end.
Empowering businesses with seamless digital accessibility solutions—simple, inclusive, effective.
Book a Demo