Labels and Accessible Forms

Correctly associate labels with their inputs, group related fields with fieldset and legend, and connect error messages to inputs so forms are usable with a screen reader, not just visually.

A form that looks perfectly clear on screen can be nearly unusable for someone navigating with a screen reader or relying on voice control, if the underlying markup doesn't explicitly connect each piece of text to the control it describes. Sighted users infer that a 'Full name' label belongs to the text box sitting next to it — but the browser and assistive technology only know that relationship exists if you tell them, using <label>, fieldset/legend, and a handful of ARIA attributes.

The label element and the for/id link

<label> associates a piece of text with a specific form control. The association is made explicit by pointing the label's for attribute at the matching input's id attribute — they must be an exact match. Once that link exists, two things improve immediately: a screen reader announces the label text whenever the input receives focus, and clicking the label text itself (not just the input) moves focus into or toggles the input — which matters enormously for small checkboxes and radios where the clickable target would otherwise be tiny.

There's an alternative, implicit form: wrapping the input inside the <label> element directly, with no for/id needed, because containment itself establishes the association. Both forms are valid; the explicit for/id version is often preferred because it doesn't constrain your layout (the input and label don't have to be nested).

Grouping related fields with fieldset and legend

When several controls belong to one logical group — a set of radio buttons for shipping speed, or the collection of fields making up a billing address — wrap them in a <fieldset>, and give that fieldset a <legend> as its very first child. The legend acts as a heading for the whole group, and screen readers announce it before reading each control inside, so a user navigating field by field hears 'Shipping speed: Standard' rather than just 'Standard' with no context.

fieldset also has a practical bonus: setting disabled on the fieldset itself disables every control nested inside it in one stroke, which is convenient for things like disabling an entire address section behind a 'ship to billing address' checkbox.

Accessible names beyond visible labels

Every form control needs what's called an 'accessible name' — the string of text a screen reader will announce for it. A properly associated <label> is the most common and most robust way to provide one. When a visible label genuinely isn't appropriate for the design (a search icon-only button, say), aria-label can supply an accessible name directly as an attribute value, and aria-labelledby can point at the id of some other element on the page whose text should serve as the name. But these are fallbacks — a real, visible <label> is almost always the better choice, because it helps sighted users too, not only screen reader users.

Marking required fields and connecting error messages

The required attribute marks a field as mandatory both functionally (the browser blocks submission until it's filled) and semantically (assistive technology announces the field as required). It's good practice to also indicate required fields visually, commonly with an asterisk, so sighted users aren't left guessing.

When a field fails validation and you show an inline error message next to it, that error text needs to be programmatically linked to the input too — otherwise a screen reader user tabbing into the field never hears the error at all. The standard technique is aria-describedby: give the error message element an id, and set the input's aria-describedby attribute to that same id. The screen reader will then read the error text as part of describing the field. Pairing this with aria-invalid="true" on the input while it's in an error state makes the problem even clearer to assistive technology.

Label association and grouping

label's for attribute must exactly match the target input's id. legend must be the first child inside fieldset. aria-describedby links an input to explanatory or error text elsewhere in the DOM.

<label for="email">Email</label>
<input type="email" id="email" name="email" required
       aria-describedby="email-error">
<span id="email-error">Please enter a valid email address.</span>

<fieldset>
  <legend>Shipping speed</legend>
  <label for="standard">Standard</label>
  <input type="radio" id="standard" name="speed" value="standard">
</fieldset>

Code Examples

Clicking the word 'Username' now moves keyboard focus into the input, and a screen reader announces 'Username, edit text' when the field receives focus — both because of the matching for/id pair.

for="username" matches id="username" No for or id required here — because the input is nested directly inside the label, the browser infers the association automatically. This is a common pattern for checkboxes and radios where the input and its text sit right next to each other.

The legend 'Billing address' is announced as context before each field inside, so a screen reader user hears 'Billing address, Street' rather than an ambiguous 'Street' with no indication which address it belongs to.

aria-describedby="pwd-error" points at the <p> holding the error text. A screen reader announces both the label and the linked error together when the field is focused, instead of leaving the error message to be discovered separately (or missed entirely).

The label's for value ('emailAddress') doesn't match the input's actual id ('email'), so the browser cannot form the association at all — clicking the label does nothing, and a screen reader announces the input with no name.

Common mistake: for and id values must match character-for-character; a typo silently breaks the entire association.

<label for="username">Username</label>
<input type="text" id="username" name="username">
<label>
  Subscribe to newsletter
  <input type="checkbox" name="newsletter">
</label>
<fieldset>
  <legend>Billing address</legend>
  <label for="street">Street</label>
  <input type="text" id="street" name="street">
  <label for="city">City</label>
  <input type="text" id="city" name="city">
</fieldset>
<label for="pwd">Password</label>
<input type="password" id="pwd" name="pwd" required
       aria-invalid="true" aria-describedby="pwd-error">
<p id="pwd-error">Password must be at least 8 characters.</p>
<label for="emailAddress">Email</label>
<input type="email" id="email" name="email">

Important Rules

Every input needs an accessible name — provided by a properly associated <label> in almost all cases.

A label's for attribute must exactly match its input's id attribute.

legend must be the first child of fieldset to be announced as the group's heading.

Link error text to its input with aria-describedby so screen reader users actually hear the error.

required communicates mandatory-field status to both the browser and assistive technology — pair it with a visible cue for sighted users too.

Why this matters in practice

Accessibility audits (and, in many jurisdictions, legal compliance requirements like WCAG) routinely flag forms with unlabeled inputs as a top-priority defect, because it can make an entire signup or checkout flow impossible to complete with a screen reader. It also has a purely practical payoff for everyone: a properly linked label enlarges the clickable area of small checkboxes and radios, which measurably reduces mis-taps on mobile.

<p>Email</p>\n<input type="email" name="email"> <label for="email">Email</label>\n<input type="email" id="email" name="email">

A plain paragraph sitting near an input looks associated visually but has no programmatic link to it at all — screen readers announce the input with no name whatsoever.

<fieldset><label for="s">Small</label><input id="s" type="radio"></fieldset> (no legend)

<fieldset><legend>Size</legend><label for="s">Small</label><input id="s" type="radio"></fieldset>

A fieldset without a legend groups the controls visually via a border but provides no announced heading, so the grouping's purpose is lost on assistive technology.

[Identify the Error] This form's error message is invisible to screen reader users even though it displays correctly on screen. What is missing? <label for="phone">Phone</label> <input type="tel" id="phone" name="phone"> <span class="error">Please enter a valid phone number.</span> A. The input needs a placeholder attribute so the error text has context B. The <span> needs an id, and the input needs aria-describedby pointing to it C. The label's for attribute is wrong and needs to point at the span instead D. Nothing is missing — labels alone always announce related error text automatically

B. The <span> needs an id, and the input needs aria-describedby pointing to it — A visible error message near an input is not automatically linked to it for assistive technology. Giving the <span> an id and setting the input's aria-describedby to that id creates the explicit link a screen reader needs to announce the error alongside the field.

Fix this snippet so the label is properly associated with the input and the field is marked required: <span>Full Name</span><input type="text" name="fullName">

<label for="fullName">Full Name</label> <input type="text" id="fullName" name="fullName" required>

Replace <span> with <label>, give the input an id, and point the label's for at that id.