Loading content...
Loading content...
Comprehensive guide to creating accessible form inputs with proper labels, error handling, and validation that meet WCAG 2.2 standards.
Text inputs are fundamental form elements that require proper labeling and error handling to meet WCAG 2.2 standards. Every input field must have an associated label that clearly identifies its purpose. The label should be programmatically associated using the forattribute or by wrapping the input within the label element. Error messages must be clearly identified, described in text, and associated with the input field using aria-describedby. The aria-invalid attribute should be set to "true" when validation errors occur. Helpful hints can be provided viaaria-describedby to guide users without cluttering the visual interface.
Testing Guide
First and last name as it appears on your ID
Email input fields require the type="email"attribute to enable proper browser validation and mobile keyboard optimization. Theautocomplete="email" attribute allows password managers and browsers to recognize and autofill email addresses, improving user experience and reducing input errors. Mobile devices will automatically display an email-optimized keyboard with the @ symbol readily accessible. Browser-level validation provides immediate feedback for invalid email formats, though server-side validation remains essential for security.
Testing Guide
We'll never share your email with anyone
Password input fields require careful attention to autocomplete attributes for proper password manager integration. Use autocomplete="new-password"for registration forms and autocomplete="current-password"for login forms. This distinction enables password managers to correctly suggest new passwords or autofill existing ones. Password requirements should be clearly communicated upfront, and real-time validation provides immediate feedback. Consider implementing a password strength indicator and a show/hide toggle for improved usability while maintaining security.
Testing Guide
Must be at least 8 characters with uppercase, lowercase, and number
Number input fields use type="number"to enable numeric input with built-in validation. The min,max, andstep attributes provide constraints that guide user input and enable browser-level validation. Mobile devices automatically display a numeric keypad when this input type is focused, significantly improving the user experience. Always provide clear hints about acceptable value ranges, and ensure error messages are specific and actionable when validation fails.
Testing Guide
Must be between 18 and 120
Telephone input fields use type="tel"to optimize mobile input by displaying a numeric keypad. Unlike number inputs, tel inputs accept various formats including dashes, parentheses, and spaces, making them ideal for international phone numbers. The autocomplete="tel"attribute enables browsers and password managers to recognize and autofill phone numbers. Placeholders can provide format hints, but they should never replace labels as placeholders disappear when users start typing and may not be announced by screen readers.
Testing Guide
Include area code
Dates are tricky! Is it MM/DD/YYYY or DD/MM/YYYY? Are we talking about the 4th of July or July 4th? This is where type="date" becomes your best friend. It provides a native date picker that respects the user's locale and system settings, eliminating the "which format?" confusion entirely. No more users entering "13/25/2024" and wondering why it's invalid. The browser handles the formatting, validation, and even provides a nice calendar UI. It's like having a personal assistant who knows exactly how dates work in your user's part of the world. Accessibility win? Absolutely. User experience win? You betcha!
Format: MM/DD/YYYY
<div className="space-y-2">
<label htmlFor="date-input">
Date of Birth <span className="text-destructive">*</span>
</label>
<input
id="date-input"
type="date"
aria-required="true"
aria-describedby="date-hint date-error"
aria-invalid="false"
/>
<p id="date-hint" className="text-xs text-muted-foreground">
Format: MM/DD/YYYY
</p>
<p id="date-error" className="text-xs text-destructive" role="alert">
Please enter a valid date
</p>
</div>Textareas are where users pour their hearts out (or at least their feedback). They're the big, spacious fields for longer content. But here's the thing: just because it's bigger doesn't mean it needs less attention! Character counters are like friendly reminders - "Hey, you've got 50 more characters before you hit the limit!" They help users stay within bounds without the frustration of hitting submit and discovering they're over. Use aria-live="polite"on the counter so screen readers announce updates without being disruptive. And thatrows attribute? Set it to a reasonable default (4-5 rows is usually perfect), but let it grow if needed. Nobody likes scrolling through a tiny box to write their novel-length complaint about shipping delays.
Maximum 500 characters
0 / 500
<div className="space-y-2">
<label htmlFor="message-input">
Message <span className="text-destructive">*</span>
</label>
<textarea
id="message-input"
rows={4}
aria-required="true"
aria-describedby="message-hint message-error message-count"
aria-invalid="false"
/>
<div className="flex justify-between">
<p id="message-hint" className="text-xs text-muted-foreground">
Maximum 500 characters
</p>
<p id="message-count" className="text-xs text-muted-foreground" aria-live="polite">
0 / 500
</p>
</div>
<p id="message-error" className="text-xs text-destructive" role="alert">
Message is required
</p>
</div>Dropdowns are the "choose your adventure" of forms. Country? Dropdown. State? Dropdown. Favorite pizza topping? You guessed it - dropdown! But here's where many forms go wrong: that first option that says "Select a country" or "Choose one..." needs to be an empty value, not just placeholder text. Why? Because when users skip it, you know they haven't made a choice yet. And always, always label your selects properly. A screen reader user navigating by form controls needs to know what they're selecting. Pro tip: if you have more than about 10 options, consider adding a search or grouping them. Nobody wants to scroll through 195 countries to find "Zambia" (though it's a lovely country, I'm sure). Keep it organized, keep it labeled, and your users will keep coming back!
Select your country of residence
<div className="space-y-2">
<label htmlFor="country-select">
Country <span className="text-destructive">*</span>
</label>
<select
id="country-select"
aria-required="true"
aria-describedby="country-hint country-error"
aria-invalid="false"
>
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
<option value="au">Australia</option>
</select>
<p id="country-hint" className="text-xs text-muted-foreground">
Select your country of residence
</p>
<p id="country-error" className="text-xs text-destructive" role="alert">
Please select a country
</p>
</div>Checkboxes are the "yes, I agree" buttons of the web. Terms and conditions? Checkbox. Newsletter subscription? Checkbox. "Remember me"? You know it - checkbox! But here's the critical part: the label must be associated with the checkbox using the forattribute (or wrap the input in the label). This isn't just for screen readers - it also makes the entire label clickable, which is a huge usability win. Ever tried to click a tiny checkbox on mobile? Yeah, not fun. But when the whole label is clickable? That's a much bigger target, and your users (and their thumbs) will thank you. And please, for the love of all that is good, don't make the checkbox required without clearly indicating it. A little asterisk andaria-required="true" go a long way!
You must agree to proceed
<div className="flex items-start space-x-2">
<input
type="checkbox"
id="terms-checkbox"
aria-required="true"
aria-describedby="terms-hint terms-error"
aria-invalid="false"
/>
<div className="space-y-1">
<label htmlFor="terms-checkbox" className="text-sm font-medium">
I agree to the terms and conditions <span className="text-destructive">*</span>
</label>
<p id="terms-hint" className="text-xs text-muted-foreground">
You must agree to proceed
</p>
<p id="terms-error" className="text-xs text-destructive" role="alert">
You must agree to the terms
</p>
</div>
</div>Radio buttons are like a multiple-choice test where you can only pick one answer. They're perfect for "choose one" scenarios, but they need to be grouped properly. Enter the fieldsetand legend - the dynamic duo of accessible radio groups! The fieldset groups related options together, and the legend describes what the group is about. Screen readers announce the legend when users enter the group, giving crucial context. Think of it like a section header in a document - it tells you what you're about to read. And here's a fun fact: all radio buttons in a group must share the same nameattribute. It's how the browser knows "these are related, only one can be selected." It's like a family dinner where only one person can talk at a time - organized chaos, but it works!
<fieldset>
<legend className="text-sm font-medium mb-3">
Preferred Contact Method <span className="text-destructive">*</span>
</legend>
<div className="space-y-3" role="radiogroup" aria-required="true">
<div className="flex items-center space-x-2">
<input type="radio" id="contact-email" name="contact" value="email" />
<label htmlFor="contact-email">Email</label>
</div>
<div className="flex items-center space-x-2">
<input type="radio" id="contact-phone" name="contact" value="phone" />
<label htmlFor="contact-phone">Phone</label>
</div>
<div className="flex items-center space-x-2">
<input type="radio" id="contact-mail" name="contact" value="mail" />
<label htmlFor="contact-mail">Mail</label>
</div>
</div>
<p id="contact-hint" className="text-xs text-muted-foreground">
Select your preferred method
</p>
<p id="contact-error" className="text-xs text-destructive" role="alert">
Please select a contact method
</p>
</fieldset>File uploads are where things can get messy (literally and figuratively). Users might try to upload a 500MB video when you only accept PDFs under 5MB. The acceptattribute is your first line of defense - it filters the file picker to show only relevant file types. But here's the thing: it's a hint, not a security measure. Always validate on the server too! And those file size limits? Tell users upfront! There's nothing more frustrating than waiting for a file to upload only to get "file too large" at the end. Be clear about what you accept, how big it can be, and what happens next. A helpful hint like "Accepted formats: PDF, DOC, DOCX. Maximum size: 5MB" sets expectations and reduces frustration. It's like putting a "maximum occupancy" sign on an elevator - everyone knows the rules before they get in!
Accepted formats: PDF, DOC, DOCX. Maximum size: 5MB
<div className="space-y-2">
<label htmlFor="file-input">
Upload Document <span className="text-destructive">*</span>
</label>
<input
id="file-input"
type="file"
accept=".pdf,.doc,.docx"
aria-required="true"
aria-describedby="file-hint file-error"
aria-invalid="false"
/>
<p id="file-hint" className="text-xs text-muted-foreground">
Accepted formats: PDF, DOC, DOCX. Maximum size: 5MB
</p>
<p id="file-error" className="text-xs text-destructive" role="alert">
Please select a valid file
</p>
</div>URLs are like addresses for the internet - they need to be formatted correctly or you'll end up in the digital equivalent of a wrong neighborhood. Using type="url"helps browsers validate the format and provides helpful hints. But here's where many developers stumble: validation. "example.com" looks like a URL, but it's missing the protocol (http:// or https://). Should you auto-add it? Should you require it? That's up to your use case, but whatever you choose, tell users! A helpful placeholder like "https://example.com" shows the expected format without being a label. And remember, autocomplete="url"helps browsers and password managers recognize this field, making form filling smoother. It's all about reducing friction and helping users get where they need to go (pun intended)!
Include http:// or https://
<div className="space-y-2">
<label htmlFor="url-input">Website URL</label>
<input
id="url-input"
type="url"
placeholder="https://example.com"
autoComplete="url"
aria-describedby="url-hint url-error"
aria-invalid="false"
/>
<p id="url-hint" className="text-xs text-muted-foreground">
Include http:// or https://
</p>
<p id="url-error" className="text-xs text-destructive" role="alert">
Please enter a valid URL
</p>
</div>Search inputs are the "where's Waldo?" helpers of your site. They help users find what they're looking for without playing hide-and-seek with your navigation. The role="searchbox"attribute tells assistive technologies "hey, this is for searching!" It's like putting a sign on a door that says "Search Here." And here's a neat trick: you can hide the label visually withsr-only (screen reader only) if the search icon and placeholder make it obvious what the field is for. But always keep the label in the code for screen readers! They need that context. The placeholder can say "Search..." but the label should say "Search" (or be more specific like "Search products"). It's the difference between a helpful hint and essential information. Both are important, but they serve different purposes!
Search for content on this site
<div className="space-y-2">
<label htmlFor="search-input" className="sr-only">
Search
</label>
<input
id="search-input"
type="search"
placeholder="Search..."
role="searchbox"
aria-label="Search"
autoComplete="off"
aria-describedby="search-hint"
/>
<p id="search-hint" className="text-xs text-muted-foreground sr-only">
Search for content on this site
</p>
</div>