Guide
Accessible form labels: how to label every field right
A form field without a label is a guessing game. Screen readers announce it as an unnamed edit box, voice control users cannot say its name, and everyone loses the hint once a placeholder disappears. This guide covers the label element, groups of options, hints and errors, autocomplete, and the mistakes that automated checks catch most often.
Use a real, visible label
The best label is a <label> element connected to its field. Connect them with for and id, or wrap the field inside the label. A connected label does three things at once: screen readers read it when the field gets focus, voice control users can say it to move to the field, and clicking the label focuses the field, which gives people with limited dexterity a larger target.
<!-- Before: a placeholder is not a label -->
<input type="email" placeholder="Email">
<!-- After: explicit association -->
<label for="email">Email</label>
<input type="email" id="email" autocomplete="email">
<!-- Also valid: wrapping -->
<label>
Email
<input type="email" autocomplete="email">
</label>A missing label fails three WCAG criteria together: 1.3.1 Info and Relationships, 3.3.2 Labels or Instructions and 4.1.2 Name, Role, Value. a11ymedic reports it under the label rule and proposes the exact label element to add, with an id on the field if it does not have one.
Why a placeholder is not a label
Placeholder only forms look clean in a mockup and cause real problems in use:
- The text disappears as soon as someone types, so people with short term memory difficulties forget what the field was for.
- When the browser autofills a form, every placeholder vanishes at once and nobody can check which value went where.
- Placeholder text is usually light gray and fails the 4.5:1 contrast minimum.
- Some screen reader and browser combinations do not announce placeholders reliably.
Keep the placeholder for an example value if you like ("name@example.com"), but always add a label. The same goes for a title attribute used as the only name: it shows only on mouse hover and never on touch screens.
Group related options with fieldset and legend
A set of radio buttons answers one question, and that question needs a label too. Without it, a screen reader user lands on a radio button that says "Yes" with no idea what they are agreeing to. Wrap the options in a <fieldset> and put the question in a <legend>. The radio-group rule flags radios that share a name but have no group label.
<fieldset>
<legend>Delivery speed</legend>
<label><input type="radio" name="speed" value="standard"> Standard (5 days)</label>
<label><input type="radio" name="speed" value="express"> Express (1 day)</label>
</fieldset>Use the same pattern for groups of checkboxes and for compound fields such as a date split into day, month and year.
Hints, required fields and error messages
Instructions that apply to a field, such as a required format, belong in visible text connected with aria-describedby. Screen readers read the description after the label. Do the same with error messages, and mark the field with aria-invalid="true" while it is wrong.
<label for="dob">Date of birth</label>
<p id="dob-hint">For example 05/09/1990</p>
<input id="dob" name="dob" inputmode="numeric" autocomplete="bday"
aria-describedby="dob-hint dob-error" aria-invalid="true" required>
<p id="dob-error">Enter the date as DD/MM/YYYY.</p>Good error messages name the field, say what is wrong and say how to fix it ("Enter the date as DD/MM/YYYY", not "Invalid input"). Do not rely on a red border alone: color by itself fails 1.4.1 Use of Color. Mark required fields in the label text or with a visible marker that is explained at the top of the form, and add the required attribute so assistive technology announces it.
Autocomplete and pasting
Fields that ask for the user's own data should use the correct autocomplete token: name, given-name, email, tel, street-address, postal-code, bday, current-password, new-password, one-time-code and so on. This is 1.3.5 Identify Input Purpose. It saves typing for people with motor disabilities and reduces errors for people with memory or cognitive disabilities. a11ymedic checks for invalid tokens (autocomplete-valid) and for personal fields that have none (input-purpose).
Never block paste. A password field with onpaste="return false" breaks password managers and forces people to transcribe long strings from memory, which fails the new WCAG 2.2 criterion 3.3.8 Accessible Authentication. The paste-blocked rule catches it.
When there is no room for a visible label
Some fields have a clear visual context, such as a search box next to a "Search" button. In that case an aria-label or aria-labelledby pointing at the button text is acceptable. Treat this as the exception. A visible label helps everyone, including people who do not use assistive technology. If you do use aria-label on a control that has visible text, the name must contain that text, ideally at the start. Otherwise voice control users who say what they see get no response. That is 2.5.3 Label in Name, checked by label-in-name. The aria-label vs aria-labelledby guide explains the difference between the two attributes.
Test your forms automatically
Form markup lives in components, so test it where it is built. The test matchers let a unit test fail as soon as someone ships a field without a label.
import { test, expect } from "vitest";
import { audit } from "a11ymedic";
import "a11ymedic/vitest";
import { renderSignupForm } from "./signup-form";
test("signup form is accessible", async () => {
const html = renderSignupForm();
expect(await audit(html)).toHaveNoViolations();
});Then do one manual check: fill the form using only the keyboard, trigger every error, and listen to it once with a screen reader. The CI guide shows how to run these tests on every pull request.
Frequently asked questions
Is a placeholder enough as a form label?
No. A placeholder disappears when the user types, is usually too low in contrast, and is not announced reliably. Use a visible label element and keep the placeholder only for an example value.
Should I use aria-label or a label element for inputs?
Use a visible label element whenever you can. It helps everyone and makes the label clickable. Use aria-label only when the purpose is already clear from the visual context, such as a search box next to a Search button.
How do I make radio buttons accessible?
Give each radio its own label, then wrap the whole set in a fieldset with a legend that asks the question. Screen readers then announce the question together with each option.
How should form errors be announced to screen readers?
Show the error as text next to the field, connect it with aria-describedby, set aria-invalid to true, and move focus to the first invalid field or to an error summary when the form is submitted.