Accessible Forms
Forms are where websites ask something of people: sign up, check out, book, apply, contact. If a form is hard to use, people cannot finish, and the cost is real: a lost order, a missed appointment, a benefit that is never claimed. Forms are also where small accessibility details matter most, because a missing label or a vague error can stop someone completely.
The HTML form controls (input, select, textarea, button) are accessible by default. Most form accessibility is just using them properly: labelling every field, grouping related ones, explaining what is needed, and telling people clearly when something goes wrong.
This lesson looks at forms through the lens of the accessibility section: names, focus, errors, and WCAG. If you want the full tour of form elements and attributes, and a step-by-step accessible form, see Accessible HTML Forms in the Forms section.
What You Will Learnβ
- How to label every field, and why placeholders are not labels
- How to group related fields with
fieldsetandlegend - How to add hints and mark required fields
- Choosing the right input type and
autocomplete - Writing error messages that help
- When to validate, and why it matters
- Keeping sign-in and long forms easy
- How to check a form
The Big Ideaβ
- HTML Code
- Field Builder
<form novalidate>
<h2>Join the bakery newsletter</h2>
<p>
<label for="name">Full name (required)</label>
<input id="name" name="name" type="text" autocomplete="name" required>
</p>
<p>
<label for="email">Email address (required)</label>
<span id="email-hint">We only use it to send your receipt.</span>
<input id="email" name="email" type="email" autocomplete="email"
required aria-describedby="email-hint">
</p>
<fieldset>
<legend>How often would you like to hear from us?</legend>
<label><input type="radio" name="often" value="weekly"> Weekly</label>
<label><input type="radio" name="often" value="monthly"> Monthly</label>
</fieldset>
<button type="submit">Subscribe</button>
</form>
We only use it to send your receipt.
HTML:
<label for="fb-email">Email address (required)</label>
<p id="fb-email-hint">We only use it to send your receipt.</p>
<input id="fb-email" name="email" type="email" autocomplete="email" required aria-describedby="fb-email-hint">
A screen reader may say: Email address, edit text, required. We only use it to send your receipt.
- This field has a visible label, a hint, a clear required marker, and the right type.
The spoken line is an example. The order and the words differ between screen readers and browsers, but the information (name, type, required, invalid, hint, error) is the same.
Change the settings one at a time, and watch both the HTML and the spoken line. Try "A placeholder only" and "Nothing" to see what goes missing.
Labels: Every Field Needs Oneβ
Every form field needs a visible, connected label. A label gives the field its accessible name (see Accessible Names), and it also makes the click area bigger: clicking the label focuses the field, and toggles a checkbox or radio button. That helps people with tremors, and anyone on a phone.
<!-- Connected with for and id -->
<label for="email">Email address</label>
<input id="email" type="email">
<!-- Or wrapped around the field -->
<label>
Email address
<input type="email">
</label>
formust match anid, and ids must be unique on the page.- Keep the label visible, and close to its field. Putting it above the field works best on small screens and when zoomed in.
- Do not use a placeholder as a label. It vanishes as you type, so people forget what to enter. It is often low-contrast, and not all tools treat it as a name. Use it, if at all, for a short example such as
name@example.com. - Do not use
titleoraria-labelinstead of a visible label, unless the purpose is obvious from context. A search box next to a "Search" button is a fair exception. - Labels should be short and specific: "Email address," not "Please enter your email address here."
Group Related Fieldsβ
When several fields answer one question, such as a set of radio buttons or an address, group them with fieldset and give the group a name with legend:
<fieldset>
<legend>Delivery method</legend>
<label><input type="radio" name="delivery" value="standard"> Standard</label>
<label><input type="radio" name="delivery" value="express"> Express</label>
</fieldset>
A screen reader reads the legend with each option: "Delivery method, Standard, radio button, 1 of 2." Without it, a person hears only "Standard," and has to guess what it is a choice of.
Use groups for radio buttons, sets of related checkboxes, and blocks of connected fields (such as "Billing address"). Do not wrap everything in a fieldset. One for a whole form of unrelated fields adds noise. Keep legends short, because they are read many times.
Hints, Instructions, and Required Fieldsβ
People should know what is needed before they type, not after they make a mistake.
- Put instructions before the field, or attach them as hints.
- Connect hints with
aria-describedby, so a screen reader reads them after the name. (You will learn more in aria-describedby.) - Tell people which fields are required, in words. An asterisk alone is not enough: many assistive tools do not announce it, and its meaning must be explained. Writing "(required)" in the label is clear to everyone. Another approach is to say once at the top that all fields are required, and mark the optional ones instead.
- Add the
requiredattribute, so browsers and assistive technology know it as well. (aria-requiredis not needed on a native field that already hasrequired.) - Show formats up front: "Date, as day/month/year" before the field, not in an error afterwards.
- Do not rely on colour alone to mark required fields or errors.
Browsers show their own built-in error bubbles for fields with required or type="email". They are useful, but the wording and the look differ by browser and language, and are hard to style. Many teams add novalidate to the form, and show their own messages in the page, as in the lessons below. If you do that, you must do it properly: the message must be text on the page, connected to the field.
The Right Type and autocompleteβ
Picking the right type changes what people get. On a phone, type="email" shows a keyboard with the @ sign, type="tel" shows a number pad, and type="url" shows a slash and a .com key. Browsers can also check the basic format.
autocomplete tells the browser, and password managers, what a field is for, so they can fill it in. This is a WCAG requirement (1.3.5 Identify Input Purpose), and it matters hugely to people who find typing hard, who have memory or cognitive difficulties, or who use assistive technology.
| Field | type | autocomplete |
|---|---|---|
| Full name | text | name |
email | email | |
| Phone | tel | tel |
| New password | password | new-password |
| Current password | password | current-password |
| One-time code | text | one-time-code |
| Street address | text | street-address |
| Postal code | text | postal-code |
| Country | select | country-name |
- Never turn
autocompleteoff for personal details or passwords. It blocks autofill and password managers, and makes forms harder, and passwords weaker. - Use
type="number"carefully. It adds spinner buttons, changes values with the mouse wheel, and does not suit things like phone or card numbers. For digits that are not a quantity, usetype="text"withinputmode="numeric". - Do not block pasting. People use password managers, and some people cannot type a long code accurately.
Errors That Helpβ
When something goes wrong, the message should tell people what is wrong, where, and how to fix it:
- Say it in text, not only with a red border or an icon (WCAG 1.4.1 and 3.3.1).
- Be specific and kind: "Enter an email address that includes an @ sign," not "Invalid input."
- Suggest a fix when you can (WCAG 3.3.3): "Did you mean name@gmail.com?"
- Connect the message to the field with
aria-describedby, and setaria-invalid="true". - Keep what the person typed. Do not clear the form after an error.
- Tell people straight away when a form fails: move focus to an error summary that links to each problem, or to the first invalid field. See Focus Management.
- Start the message with a word such as "Error:", so it is clear without colour.
<label for="email">Email address (required)</label>
<input id="email" type="email" required
aria-invalid="true" aria-describedby="email-error">
<p id="email-error"><strong>Error:</strong> Enter an email address that includes an @ sign.</p>
When to Validateβ
Checking a field too early can feel like being nagged while you are still typing. For a screen reader user, a message that is announced after every key is overwhelming. The lab below shows the difference.
- HTML Code
- Timing Lab
<label for="email">Email address</label>
<input id="email" type="email" aria-describedby="email-error">
<p id="email-error" aria-live="polite"></p>
<script>
const input = document.getElementById("email");
const error = document.getElementById("email-error");
// Check when the person leaves the field, not on every key
input.addEventListener("blur", () => {
const valid = input.value.includes("@");
input.setAttribute("aria-invalid", String(!valid));
error.textContent = valid ? "" : "Error: Include an @ sign in the email address.";
});
</script>
Error messages announced so far: 0
Recent messages: (none yet)
Type a@b.co slowly in each mode. In the first mode, a screen reader user hears a new error after nearly every key, before they have had a chance to finish. Waiting until the person leaves the field, or presses Submit, is usually kinder. A good pattern is to show an error after someone leaves a field, and to remove it as soon as they fix it. Counting messages here is a simulation of what a polite live region would announce, and real screen readers may combine or skip some of them.
Good habits for timing:
- Wait until the person leaves the field, or presses Submit, before showing a first error.
- Remove the error as soon as it is fixed, while they type. People then know it worked.
- Do not interrupt text entry. For languages that use an input method editor (such as Chinese, Japanese, or Korean), never validate in the middle of composing a character.
- Use a polite live region (
aria-live="polite") for messages that appear while someone is in a field, and an error summary (with focus) for a failed submit. See ARIA Live Regions.
Help People Avoid Mistakesβ
WCAG 2.2 has a few more rules that make forms kinder, especially for people with memory, attention, or cognitive differences:
- Review before you finish (3.3.4): for payments, legal agreements, and anything that changes data, let people check, confirm, or undo before the final submit.
- Do not ask twice (3.3.7 Redundant Entry): if you already know something from earlier in the same process (such as a delivery address), fill it in again, or offer "same as delivery address."
- Accessible sign-in (3.3.8): do not require people to remember or transcribe something to log in (such as solving a puzzle, or retyping a code from memory). Allow paste, support password managers, and offer an alternative to anything difficult.
- Give enough time: if a form times out, warn people and let them extend it. Save their progress where you can.
Other Controls and Layoutβ
- Choose native controls:
select,input type="checkbox",type="radio", andtextarea. They have keyboard support and screen reader support already. A custom dropdown must be rebuilt with great care. - Dates: a native
input type="date"is accessible in most browsers, but its look varies. Three separate, clearly labelled fields for day, month, and year can be easier for known dates, such as a birth date. - File uploads: say which types and sizes are allowed before the upload. After upload, say what was chosen.
- Character counters: do not announce every character. Announce when a limit is near, and when it is reached.
- One column, with labels above the fields, works best on phones and when zoomed. The reading order must match the visual order.
- Make the submit button clear, and put it last. Name it with a specific action: "Place order," not "Submit."
- Check at 400 percent zoom. The form must reflow, with no horizontal scrolling and no hidden fields.
Check Your Formsβ
The checker below reads a snippet of HTML and flags fields with no label, placeholder-only fields, mismatched label ids, wrong input types, missing or disabled autocomplete, blocked paste, ungrouped radio buttons, and invalid fields with no message.
Then test the real form by hand:
- Click each label. Does the field get focus?
- Tab through the whole form, in order. Is every field reachable, with a visible focus?
- Submit it empty. Do you learn what is wrong, where, and how to fix it?
- Fill it with a keyboard only, including the submit.
- Use a screen reader on one pass. Are names, groups, hints, and errors announced?
- Zoom to 200 and 400 percent.
- Try autofill and a password manager.
Good Habitsβ
- Label every field with a visible, connected
label. - Group related fields with
fieldsetandlegend. - Say what is required, in words, and give formats before the field.
- Use the right
typeandautocomplete. - Write errors that say what, where, and how to fix.
- Validate when people leave a field, not on every key.
- Keep what people have typed, and never clear the form after an error.
- Allow paste, and support password managers.
- Ask only for what you need, and never ask twice.
- Test with a keyboard, a screen reader, and a phone.
Accessibility Checklistβ
- Every field has a visible, connected label.
- Related fields are in a
fieldsetwith alegend. - Required fields are marked in text, and have the
requiredattribute. - Hints and formats are shown before the field, and connected with
aria-describedby. - Each field has a suitable
typeandautocompletevalue. - Errors are shown in text, say how to fix the problem, and are linked to the field.
- After a failed submit, focus moves to an error summary or the first problem.
- Errors do not appear while someone is still typing.
- Paste is allowed, and nothing in sign-in needs memory or a puzzle.
- People can review, confirm, or undo important submissions.
- The form works with a keyboard, and at 400 percent zoom.
Security and Privacy Notesβ
- Ask for as little as you can. Every field is data you must protect, and a burden for the person. Never ask for sensitive information (such as health, religion, or ID numbers) unless it is necessary.
- Never put sensitive data in the URL. Use
method="post"for passwords and personal details. AGETform puts values in the address, history, and logs. - Do not disable
autocompletefor passwords. Password managers make people safer. Usenew-passwordandcurrent-password, andone-time-codefor codes. - Do not block paste in password or code fields. It pushes people to use weaker, easier passwords.
- Be careful with error messages at sign-in. Saying "No account with that email" reveals who has an account. Many sites say "The email or password is incorrect." Weigh the privacy gain against the added confusion, and explain it in the page.
- Anti-spam traps ("honeypots") are hidden fields that real people should not fill in. If you use one, hide it from assistive technology too (
aria-hidden="true"andtabindex="-1"), or screen reader users will fill it in, and be treated as bots. - CAPTCHAs can block people who are blind, have low vision, or have cognitive differences. Prefer invisible checks, or offer an accessible alternative, and check what data a third-party service collects.
- Consent boxes (marketing, cookies) must never be pre-ticked, and should be separate from the terms.
- Use HTTPS for every page with a form, not only the page that receives it.
Global Audience Notesβ
- Do not force a first name and last name. Many people have one name, several, or a different order. One "Full name" field is often best, with
autocomplete="name". - Addresses differ by country: some have no postal code, no state, or an unusual order. Make fields optional where you can, or change them by country.
- Phone numbers: accept spaces, dashes, and the
+country code. Do not demand one local format. - Dates and numbers: formats vary (day-month-year, month-day-year, decimal points and commas). Show an example and accept sensible forms, or use a native date control.
- Right-to-left languages: mirror the layout, and use
dir="auto"ordir="ltr"for email, URL, and number fields, so that Latin text does not scramble. - Translate everything: labels, hints, errors, and
aria-labelvalues. Leave room for longer text, and avoid fixed-width labels. - Input methods: people who type with an input method editor need freedom to compose text. Validate after the text is committed, not during.
- Avoid assumptions in titles, gender options, or required "marital status." Provide an open text field, or "prefer not to say," and only if you truly need to ask.
- Slow or unreliable connections: keep entered data if a submit fails, and show clear progress.
Common Mistakes to Avoidβ
- Fields with only a placeholder.
- Labels that are not connected (a missing or mismatched
forandid). - Radio buttons with no
fieldsetandlegend. - Marking required fields only with a red asterisk.
- Error messages that say only "Invalid."
- Showing an error on every key press.
- Clearing the form after an error.
autocomplete="off"on names, emails, and passwords.- Blocking paste.
- Using
type="text"for email and phone. - Submit buttons that just say "Submit."
- Never testing with a keyboard or a screen reader.
Practice Timeβ
Try these in your browser and your editor:
- Use the field builder to build all four field types, and read the spoken lines.
- In the timing lab, type a valid email in each mode, and count the messages.
- Run the checker on the messy form, fix every problem, and run it again.
- Take a form you have built, and click every label. Does each field get focus?
- Wrap a group of radio buttons in a
fieldsetandlegend, and compare how it looks and sounds. - Add
autocompletevalues to a sign-up form, and test autofill. - Replace a placeholder-only field with a visible label and a hint.
- Write three error messages that say what, where, and how to fix.
- Add an error summary that links to each invalid field. (See Focus Management.)
- Bonus: fill in a long form using only the keyboard, and note every moment where you got stuck.
Quick Recapβ
Click a question to check your answer.
1. Why is a placeholder not a good label?
It disappears when you type, usually has low contrast, and is only a weak fallback name. Use a visible label element.
2. When do you use fieldset and legend?
For groups of related fields that answer one question, such as radio buttons, related checkboxes, or an address. The legend is read with each option.
3. How do you attach a hint to a field?
Give the hint text an id, and put that id in the field's aria-describedby. The screen reader reads it after the name.
4. Why use the right type and autocomplete?
The right type gives the right keyboard and basic checks, and autocomplete lets browsers and password managers fill the field. It is also a WCAG requirement (1.3.5).
5. What makes a good error message?
It is text (not only colour), says what is wrong and how to fix it, is connected to the field with aria-describedby and aria-invalid, and keeps what the person typed.
6. When should a first error appear?
Usually when the person leaves the field, or presses Submit, and not on every key press. Remove the error as soon as it is fixed.
Key Takeawaysβ
- Every field needs a visible, connected label. A placeholder is not one.
- Group related fields with
fieldsetandlegend. - Tell people what is needed before they type: required fields, formats, and hints.
- Use the right
typeandautocomplete. Never block paste or autofill. - Errors must say what, where, and how to fix it, in text, linked to the field.
- Validate after people leave a field, and keep their data when something fails.
- Make sign-in and long forms forgiving: review steps, no repeat entry, no puzzles.
- Test the real form with a keyboard, a screen reader, and zoom.
Next, learn how to make data tables that make sense to everyone in Accessible Tables.