How to Create Accessible Forms: HTML5 & ARIA Tutorial
Forms are where accessibility becomes critical. When a user can't complete your checkout form, submit a contact request, or create an account, that's not just an accessibility failure—it's lost business. Yet forms remain one of the most common sources of accessibility barriers on the web.
The good news is that building accessible forms isn't complicated. Most accessibility comes from using HTML correctly, and the techniques covered here will make your forms better for everyone—not just users with disabilities. This tutorial covers everything from basic label associations to complex validation patterns, giving you the knowledge to build forms that work for all users.
The Foundation: Labels and Inputs
Every form input needs a label. This seems obvious, but it's the most frequently violated rule in form accessibility. Without a label, screen reader users have no idea what information a field expects. They hear "edit text" without context, forced to guess whether the field wants an email address, phone number, or something else entirely.
The proper way to associate labels with inputs uses the for attribute:
<label for="email">Email Address</label>
<input type="email" id="email" name="email">The for attribute value must match the input's id exactly. This creates a programmatic association that assistive technologies understand. Screen readers announce "Email Address, edit text" when users reach this field—clear and unambiguous.
This association also benefits mouse users. Clicking the label text focuses the associated input, making form fields easier to target. For checkboxes and radio buttons, this larger click target significantly improves usability.
Wrapping Labels
An alternative pattern wraps the input inside the label element:
<label>
Email Address
<input type="email" name="email">
</label>This approach doesn't require matching for and id attributes—the association is implicit. However, the explicit method with for/id is generally preferred because it allows more flexible positioning and styling. Both methods are valid and accessible.
Placeholder Text Isn't a Label
A common mistake is using placeholder text instead of a label:
<!-- WRONG: No label -->
<input type="email" placeholder="Email Address">Placeholder text disappears when users start typing, leaving them with no reminder of what the field wants. It also typically has insufficient contrast and can't be styled as needed. Users with cognitive disabilities may find disappearing placeholders particularly confusing.
Use placeholders only as supplementary hints, never as replacements for labels:
<!-- CORRECT: Label plus helpful placeholder -->
<label for="phone">Phone Number</label>
<input type="tel" id="phone" placeholder="(555) 123-4567">Grouping Related Fields
Forms with multiple related fields need additional structure. The fieldset element groups related inputs, and legend provides a label for the group. This structure is essential for radio buttons and checkboxes that share a common question:
<fieldset>
<legend>Preferred Contact Method</legend>
<label>
<input type="radio" name="contact" value="email">
Email
</label>
<label>
<input type="radio" name="contact" value="phone">
Phone
</label>
<label>
<input type="radio" name="contact" value="mail">
Mail
</label>
</fieldset>Screen readers announce the legend when users enter the group, providing context for the individual options. Without the fieldset/legend structure, users hear only "Email, radio button, not selected"—they don't know what question they're answering.
Fieldsets work well for any related group of fields, not just radio buttons. Shipping address fields, credit card information, and date of birth fields all benefit from fieldset grouping:
<fieldset>
<legend>Shipping Address</legend>
<label for="street">Street Address</label>
<input type="text" id="street" name="street">
<label for="city">City</label>
<input type="text" id="city" name="city">
<label for="state">State</label>
<select id="state" name="state">
<!-- State options -->
</select>
<label for="zip">ZIP Code</label>
<input type="text" id="zip" name="zip">
</fieldset>Indicating Required Fields
Users need to know which fields they must complete. Visual indicators like asterisks help sighted users, but you also need to communicate this programmatically.
The required attribute is the HTML5 solution:
<label for="email">Email Address</label>
<input type="email" id="email" required>The aria-required attribute achieves similar results for screen readers:
<label for="email">Email Address</label>
<input type="email" id="email" aria-required="true">For the best user experience, combine visual and programmatic indicators:
<label for="email">
Email Address
<span class="required" aria-hidden="true">*</span>
</label>
<input type="email" id="email" required aria-required="true">The asterisk is hidden from screen readers (via aria-hidden) because the required attribute already conveys this information. Including both would result in redundant announcements.
If your form has mostly required fields with only a few optional ones, consider flipping the pattern—mark optional fields instead:
<label for="company">
Company Name
<span class="optional">(optional)</span>
</label>
<input type="text" id="company">Either approach works as long as you're consistent throughout the form.
Providing Instructions and Hints
Complex fields often need additional explanation. The aria-describedby attribute connects inputs with their instructions:
<label for="password">Password</label>
<input type="password"
id="password"
aria-describedby="password-requirements">
<p id="password-requirements">
Password must be at least 8 characters with one uppercase letter,
one number, and one special character.
</p>When a screen reader user focuses this field, they hear both the label and the description: "Password, edit text, Password must be at least 8 characters..."
This pattern works for any field that benefits from additional context—date formats, character limits, examples of valid input, or explanations of why you're asking for certain information.
You can reference multiple descriptions by space-separating IDs:
<input type="text"
id="promo-code"
aria-describedby="promo-hint promo-example">
<p id="promo-hint">Enter your promotional code if you have one.</p>
<p id="promo-example">Example: SAVE20</p>Handling Errors
Error handling can make or break form accessibility. When validation fails, users need to understand what went wrong and how to fix it. Vague errors like "Please correct the errors below" leave users frustrated and lost.
Inline Errors
The most effective pattern places error messages directly next to the fields they describe:
<div class="form-field error">
<label for="email">Email Address</label>
<input type="email"
id="email"
aria-invalid="true"
aria-describedby="email-error">
<p id="email-error" class="error-message">
Please enter a valid email address (example: name@domain.com)
</p>
</div>The aria-invalid="true" attribute tells screen readers this field has an error. Combined with aria-describedby pointing to the error message, users get clear information about what's wrong.
Notice the error message includes an example of valid input. "Please enter a valid email address" is helpful; "Please enter a valid email address (example: name@domain.com)" is better. Always help users understand what correct input looks like.
Error Summaries
For long forms, provide an error summary at the top in addition to inline errors. This summary helps users understand the scope of problems without scrolling through the entire form:
<div role="alert" id="error-summary" tabindex="-1">
<h2>Please correct the following errors:</h2>
<ul>
<li><a href="#email">Email address is required</a></li>
<li><a href="#phone">Phone number format is invalid</a></li>
</ul>
</div>The role="alert" ensures screen readers announce this content when it appears. Linking each error to its field lets users jump directly to problems. After submission fails, move focus to this summary so users don't miss it:
function showErrors(errors) {
const summary = document.getElementById('error-summary');
// Populate error list...
summary.focus();
}Live Validation
Real-time validation can improve user experience, but implement it carefully. Validating on every keystroke frustrates users who haven't finished typing. Instead, validate when users leave a field (blur event) or pause typing (debounced input):
const emailInput = document.getElementById('email');
emailInput.addEventListener('blur', function() {
validateEmail(this);
});
function validateEmail(input) {
const valid = input.value.includes('@') && input.value.includes('.');
if (!valid && input.value.length > 0) {
input.setAttribute('aria-invalid', 'true');
showError(input, 'Please enter a valid email address');
} else {
input.removeAttribute('aria-invalid');
hideError(input);
}
}This validates when users tab away from the field, giving them time to complete their input first.
Input Types and Autocomplete
HTML5 input types improve both accessibility and usability by triggering appropriate virtual keyboards on mobile devices and enabling browser autofill:
<input type="email"> <!-- Email keyboard with @ -->
<input type="tel"> <!-- Numeric phone keyboard -->
<input type="url"> <!-- URL keyboard with .com -->
<input type="number"> <!-- Numeric keyboard -->
<input type="date"> <!-- Date picker -->The autocomplete attribute helps browsers and password managers fill forms automatically, saving users time and reducing errors:
<label for="name">Full Name</label>
<input type="text" id="name" autocomplete="name">
<label for="email">Email</label>
<input type="email" id="email" autocomplete="email">
<label for="address">Street Address</label>
<input type="text" id="address" autocomplete="street-address">
<label for="cc-number">Card Number</label>
<input type="text" id="cc-number" autocomplete="cc-number">Proper autocomplete values are especially important for users with motor or cognitive disabilities who find form completion challenging. They're also required by WCAG 2.1 Success Criterion 1.3.5 (Identify Input Purpose) for common personal information fields.
Button Accessibility
Form buttons need clear, descriptive text. "Submit" is acceptable but generic; "Create Account" or "Place Order" tells users exactly what will happen:
<!-- Generic but acceptable -->
<button type="submit">Submit</button>
<!-- Better: Describes the action -->
<button type="submit">Create Account</button>If you must use icon-only buttons, provide accessible names:
<!-- Screen reader accessible icon button -->
<button type="submit" aria-label="Search">
<svg aria-hidden="true"><!-- search icon --></svg>
</button>The aria-label provides the accessible name; aria-hidden="true" on the SVG prevents it from being announced.
Avoid <input type="submit"> when possible—actual <button> elements allow richer content and are easier to style. If you do use input, ensure the value attribute provides meaningful text:
<input type="submit" value="Subscribe to Newsletter">Putting It All Together
Here's a complete accessible form incorporating all these techniques:
<form id="contact-form" novalidate>
<h2>Contact Us</h2>
<p class="form-intro">
Required fields are marked with an asterisk (*).
</p>
<div class="form-field">
<label for="name">
Full Name
<span aria-hidden="true">*</span>
</label>
<input type="text"
id="name"
name="name"
required
autocomplete="name">
</div>
<div class="form-field">
<label for="email">
Email Address
<span aria-hidden="true">*</span>
</label>
<input type="email"
id="email"
name="email"
required
autocomplete="email"
aria-describedby="email-hint">
<p id="email-hint" class="hint">
We'll never share your email with anyone else.
</p>
</div>
<div class="form-field">
<label for="message">
Your Message
<span aria-hidden="true">*</span>
</label>
<textarea id="message"
name="message"
required
rows="5"></textarea>
</div>
<fieldset>
<legend>How should we respond?</legend>
<label>
<input type="radio" name="response" value="email" checked>
Email reply (within 24 hours)
</label>
<label>
<input type="radio" name="response" value="phone">
Phone call (within 48 hours)
</label>
</fieldset>
<button type="submit">Send Message</button>
</form>This form uses explicit labels, groups related fields, marks required fields, provides hints where helpful, and uses descriptive button text. It's the foundation for accessible form design.
Taking Action
Accessible forms start with proper HTML semantics—labels, fieldsets, and appropriate input types. Layer on helpful instructions, clear error messages, and consistent patterns. Test with keyboard navigation and screen readers to verify the experience works for all users.
TestParty identifies form accessibility issues across your site, catching missing labels and improper error handling before they affect users.
Schedule a TestParty demo and get a 14-day compliance implementation plan.
Related Resources
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