Topic 4.3
Labels, Fieldsets and Legends
In one line
A <label> ties visible text to a control so everyone knows what it's for and can click the text to use it; a <fieldset> with a <legend> groups related controls under one shared caption.
Think of it like this
Name tags at a party, and tables with a sign. A label is a name tag stuck on one guest: you always know who's who. A fieldset is a table with a sign on it ("Family", "Work friends"): the sign, the legend, tells you what everyone at that table has in common.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- label
- The element that holds the visible name of a control and connects to it.
- for / id
- The label's
formust equal the control'sid. That is how the browser links them. - Implicit label
- A label that wraps its control:
<label>Email <input></label>. Nofororidneeded. - Accessible name
- The name of a control that screen readers announce, usually taken from its label.
- Screen reader
- Software that reads the page aloud (or as braille) for people who can't see the screen.
- fieldset
- A box that groups related controls together.
- legend
- The caption of a fieldset, describing the whole group.
- Visually hidden
- Hidden on screen with CSS but still read by screen readers, unlike
display: none.
Step by step
01An explicit label
The label and the input are separate elements, linked by for and id. They can sit anywhere in the form, which makes layout with CSS Grid easy.
<label for="email">Email</label>
<input id="email" name="email" type="email">02An implicit (wrapping) label
Here the input lives inside the label, so no id is needed. This is common for checkboxes: the whole line of text becomes clickable.
Some older assistive tools handle only for/id well, so many teams use both: wrap the control and add for/id. It never hurts.
<label>
<input type="checkbox" name="news">
Send me the newsletter
</label>03Group with a fieldset
The legend names the group. Each control inside still has its own label.
<fieldset>
<legend>Delivery address</legend>
<label for="street">Street</label>
<input id="street" name="street">
<label for="city">City</label>
<input id="city" name="city">
</fieldset>04What a screen reader hears
Moving into the street box, a screen reader says something like "Delivery address, group. Street, edit text." Without the label it would say only "edit text"; without the legend it couldn't tell this street from a billing street.
You can see the same information in DevTools: select the input, open the Accessibility pane and look at Name (Topic 7.6).
05Hide a label only from the eyes
When the design has no room for visible text, keep the label for screen readers with a visually hidden class. This pattern is used by most design systems.
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}Try it yourself
- 1
Fix the phone field
In the first example, turn the
<span>into<label for="phone">. Predict the second console line, then run it: it now saysphone has 1 labels, and clicking "Phone" focuses the box. - 2
Break the link on purpose
Change
for="email"tofor="Email"(capital E). Run it. Which line changes? (idmatching is case-sensitive, so the label is no longer linked andemail.labels[0]is undefined.) - 3
Keep a control inside the legend enabled
In the second example, put
<input type="checkbox" name="gift">inside the<legend>. Run it:giftis still sent after the fieldset is disabled, because controls in the first legend are exempt.
Code & diagrams
Click the words in the preview yourself: "Email" focuses the box, the newsletter text ticks the box, "Phone" does nothing.
Preview
Console
Nothing logged yet.Expected console output
email has 1 label: Email
phone has 0 labels
Focused after clicking the label: email
Checkbox ticked by clicking its text: trueThe inputs inherit "disabled" from their fieldset, even though their own `disabled` property stays false.
Preview
Console
Nothing logged yet.Expected console output
Before: [ 'name', 'street', 'city' ]
After disabling the fieldset: [ 'name' ]
city.disabled: false but matches :disabled? true
Controls in the fieldset: 2Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Duplicate ids
Give two inputs the same id="name" and point a label at it with for="name".
Break #2
Hide the label with display: none
Add label { display: none; } to make a compact search box.
Myth vs fact
Myth
Labels are only for screen readers.
Fact
Everyone benefits: a visible label explains the field, and clicking it focuses the box or ticks the checkbox, giving a bigger target on touch screens.
Myth
A fieldset is just a box with a border.
Fact
Its legend is announced to screen readers as the group's name, and disabled on it switches off every control inside. The border is only its default look and can be removed with CSS.
Myth
You can put anything inside a label.
Fact
A label should contain text and at most one control. Putting links or other buttons inside makes clicks ambiguous and confuses assistive technology.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
A control can have several labels;
input.labelslists them all and the accessible name joins their text. The name is computed by the Accessible Name and Description Computation spec:aria-labelledbywins overaria-label, which wins over<label>, thentitle, thenplaceholderas a last resort (Topic 7.5). - ▸
The browser's default style gives
<fieldset>min-inline-size: min-content, so wide content inside it can overflow a grid or flex parent instead of shrinking. Addmin-width: 0(ormin-inline-size: 0) to fix it. - ▸
Only the first
<legend>child of a fieldset is its caption and is drawn over the border. Its rendering is special (it sits on the border line), which is why some CSS likedisplay: flexbehaves differently on a legend and on the fieldset in older browsers. - ▸
Labelled controls are also easier to test: tools like Playwright and Testing Library find fields with
getByLabel("Email"), so good labels make tests readable and accessibility bugs visible.
Remember this
- 1
A label is the
<label>element. Connect it to a control in one of two ways. Explicit: give the control anidand the label a matchingfor(<label for="email">+<input id="email">). Implicit: put the control inside the label (<label>Email <input></label>). Theforvalue must match theidexactly, including capital letters, and theidmust be unique in the page. - 2
What a label gives you: clicking or tapping the label text focuses the box, or ticks a checkbox or radio button. That makes the target much bigger, which matters on phones and for people with shaky hands. Screen readers read the label text as the control's accessible name: the name announced when you reach the control, such as "Email, edit text". Without it they announce only "edit text", and the person has to guess.
- 3
Every control needs a name a person can see. If a design really has no visible text (say a search box next to a magnifying-glass button), keep a
<label>and hide it visually only with a "visually hidden" CSS class, or usearia-label(Topic 7.4). Never hide it withdisplay: none, which hides it from screen readers too. - 4
A fieldset is the
<fieldset>element: a box around a group of related controls, such as the parts of an address or a set of radio buttons. Its first child should be a legend, the<legend>element, the caption for the whole group. Screen readers announce the legend when you move into the group, so "Delivery address, Street" and "Billing address, Street" are told apart. - 5
Use a fieldset whenever one question has several controls (radio buttons and checkbox groups always need one, Topic 4.4) or when a long form has sections. Don't wrap a single input in a fieldset; its label is enough.
- 6
A fieldset can be disabled as a whole:
<fieldset disabled>switches off every control inside it at once, so none of them can be used or are sent. This is handy for "Same as delivery address" sections. Controls inside the fieldset's first<legend>stay enabled.
Explain it without notes
What are the two ways to connect a label to a control, and what does each need?
Why do labels matter for accessibility and for usability?
When should you use a fieldset and legend?
What does disabled on a fieldset do?
Practice
Write a login form with properly labelled username and password fields, using for/id.
Group two radio-style questions: a fieldset with the legend Contact me by, containing labelled checkboxes for Email and SMS.
Trade-offs
- ↔
Wrapping labels need no ids and are quick to write, but make CSS layouts where the label and box sit in different grid cells harder;
for/idlabels are more flexible but need unique ids, which is harder in reused components (generate them, e.g. with React'suseId).
Done when you can
I can label every control with
for/idor by wrapping.I can explain what an accessible name is and where it comes from.
I can group related controls with a fieldset and legend.
I can hide a label visually without hiding it from screen readers.
I know what
disabledon a fieldset does to the controls inside.