A field without a label is a field that some people cannot use at all. Screen readers announce an unlabelled input as "edit text", with no clue what belongs in it, and a sighted visitor who cannot see the surrounding text has the same problem. Labels are the difference between a form that works and a form that quietly excludes people.
How does a label work?
A label is a piece of text tied to a control. The tie is made with the for attribute, which holds the id of the control, and the control has the matching id.
<label for="email">Email address</label>
<input type="email" id="email" name="email">
Two things happen when they are tied together. Clicking the label moves focus to the control, which is a small but real convenience for anyone aiming at a small target. And the accessible name of the control becomes the label text, which is what a screen reader announces.
Does the label have to be outside the input?
No. A label can wrap the control it labels, and then the for attribute is unnecessary.
<!-- the for/id pair -->
<label for="email">Email address</label>
<input type="email" id="email" name="email">
<!-- the wrapping form -->
<label>Email address
<input type="email" name="email">
</label>
Both work. The explicit pair is easier to style, easier to place separately from the control, and easier to check with a validator, so it is the one to reach for by default. What you must never do is use both on the same control with different text, because the browser resolves the conflict in a way that surprises people.
Why is placeholder text not a label?
This is the mistake every beginner makes once: put the hint in the placeholder, skip the label, and the field looks finished. It is not, and there are three separate reasons.
<!-- wrong: no accessible name at all -->
<input type="email" name="email" placeholder="Email address">
<!-- right -->
<label for="email">Email address</label>
<input type="email" id="email" name="email">
First, the placeholder disappears as soon as the visitor types, so the hint is gone exactly when someone with a low vision or a memory problem needs it most. Second, screen readers treat placeholder text inconsistently, and several do not announce it at all — the field is announced with no name. Third, the contrast between a light grey placeholder and a white background is usually far below the accessibility minimum, so the hint is hard to read even for sighted users.
A placeholder is fine as a format example on top of a real label — "[email protected]" for an email field — as long as the label still exists and the placeholder is not the only description of the field.
How do you label the other controls?
Every control needs the same treatment, and the for and id pair works for all of them.
<label for="plan">Choose a plan</label>
<select id="plan" name="plan">
<option value="free">Free</option>
<option value="pro">Pro</option>
</select>
<fieldset>
<legend>Notifications</legend>
<label for="email-updates">
<input type="checkbox" id="email-updates" name="notifications" value="email">
Email me about new lessons
</label>
<label for="sms-updates">
<input type="checkbox" id="sms-updates" name="notifications" value="sms">
Text me about new lessons
</label>
</fieldset>
Two things to note. A group of related checkboxes or radios belongs in a fieldset with a legend, which gives the group a name and lets a screen reader announce the group before each option. And a legend is not a label: it names the group, while each option still needs its own label.
What about aria-label?
The aria-label attribute sets an accessible name directly, and it works — but it is the second-best tool. A visible label helps everyone: it is what a sighted user reads, what a screen reader reads, and what survives a translation. An aria-label is invisible on the page, so it adds a name that only assistive technology sees, and it means the visual label and the spoken label can drift apart.
<!-- visible label, tied to the control -->
<label for="search">Search the tutorials</label>
<input type="search" id="search" name="q">
<!-- aria-label: only when there is no visible text to use -->
<button aria-label="Close dialog">×</button>
Reach for aria-label when the control is an icon with no text — a close button, a search icon — and there is genuinely nothing visible to label. In every other case, use a real label element.
How do you group related fields?
Some forms ask for the same kind of thing several times. A delivery address might need a street, a city and a postcode; a phone number might need a country code and a local number. Wrapping those in a fieldset with a legend gives the group a name that every screen reader announces before the first field inside it.
<fieldset>
<legend>Delivery address</legend>
<label for="street">Street and number</label>
<input id="street" name="street" autocomplete="street-address">
<label for="city">City</label>
<input id="city" name="city" autocomplete="address-level2">
<label for="postcode">Postcode</label>
<input id="postcode" name="postcode" autocomplete="postal-code">
</fieldset>The fieldset element is not only an accessibility tool. Browsers style it with a border and a legend by default, which is a quick way to draw a labelled group of controls, and you can remove the default border in CSS if the design does not want one.
The autocomplete attributes in that example are worth adding to every field you can. They let a browser fill the form from a saved address or a stored card, which is faster for everyone and removes a whole class of typing errors.
For a set of radio buttons that belong together, the grouping comes from a shared name attribute rather than from a fieldset - but you still want a fieldset around them, because the group needs a label that explains what the choice is for.
Quick reference
- Tie a label to a control with a matching
forandid, or wrap the control in the label. - A placeholder is a hint, never a label: it vanishes, it is ignored by some screen readers, and its contrast is usually too low.
- Groups of checkboxes and radios need a
fieldsetand alegend. - Prefer a visible label over
aria-label; usearia-labelfor icon-only controls. - A click on the label focuses the control, which is a usability win as well as an accessibility one.
Try it yourself: build a form where every control has a visible label, then check the accessibility tree in the developer tools and read the computed name of each control. Remove one label and watch the name disappear. Then replace a label with a placeholder and check the name again — it will still be missing.