Command Palette

Search for a command to run...

Hectal
PHASE 4Intermediate ~19 min· topic 3 of 8

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 for must equal the control's id. That is how the browser links them.
Implicit label
A label that wraps its control: <label>Email <input></label>. No for or id needed.
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.

index.htmlwhole filehtml
<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.

index.htmlwhole filehtml
<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.

index.htmlwhole filehtml
<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>
Group with a fieldsetdiagram
Rendering diagram…

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.

style.csswhole filecss
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

Try it yourself

  1. 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 says phone has 1 labels, and clicking "Phone" focuses the box.

  2. 2

    Break the link on purpose

    Change for="email" to for="Email" (capital E). Run it. Which line changes? (id matching is case-sensitive, so the label is no longer linked and email.labels[0] is undefined.)

  3. 3

    Keep a control inside the legend enabled

    In the second example, put <input type="checkbox" name="gift"> inside the <legend>. Run it: gift is still sent after the fieldset is disabled, because controls in the first legend are exempt.

Code & diagrams

Labels in action New tab

Click the words in the preview yourself: "Email" focuses the box, the newsletter text ticks the box, "Phone" does nothing.

Preview

Press Run to see the page.

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: true
A fieldset switches a whole group off New tab

The inputs inherit "disabled" from their fieldset, even though their own `disabled` property stays false.

Preview

Press Run to see the page.

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: 2

Break 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".

terminal
$ clicking the second label
── what you'll see ──
focus jumps to the first input instead

Break #2

Hide the label with display: none

Add label { display: none; } to make a compact search box.

terminal
$ DevTools → Accessibility → Name
── what you'll see ──
(empty) a screen reader announces only "edit text"

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.labels lists them all and the accessible name joins their text. The name is computed by the Accessible Name and Description Computation spec: aria-labelledby wins over aria-label, which wins over <label>, then title, then placeholder as 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. Add min-width: 0 (or min-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 like display: flex behaves 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. 1

    A label is the <label> element. Connect it to a control in one of two ways. Explicit: give the control an id and the label a matching for (<label for="email"> + <input id="email">). Implicit: put the control inside the label (<label>Email <input></label>). The for value must match the id exactly, including capital letters, and the id must be unique in the page.

  2. 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. 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 use aria-label (Topic 7.4). Never hide it with display: none, which hides it from screen readers too.

  4. 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. 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. 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

01

What are the two ways to connect a label to a control, and what does each need?

02

Why do labels matter for accessibility and for usability?

03

When should you use a fieldset and legend?

04

What does disabled on a fieldset do?

Practice

01

Write a login form with properly labelled username and password fields, using for/id.

02

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/id labels are more flexible but need unique ids, which is harder in reused components (generate them, e.g. with React's useId).

Done when you can

  • I can label every control with for/id or 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 disabled on a fieldset does to the controls inside.