Accessibility · Forms and Errors

Fieldset and Legend Group Semantics

How fieldset and legend give a set of controls one shared name, how the browser exposes that grouping, and where the element falls short of what a group needs.

A radio group is the clearest case where per-control labelling is not enough. Each option is labelled correctly: “Standard shipping”, “Express shipping”, “Collect in store”. A screen reader user tabs into the group and hears the first option’s name and state, then the second, then the third. The question that the three options answer, how the order should be delivered, is never spoken, because it was written as a paragraph above the group and a paragraph has no relationship to the radio buttons below it. Three well-labelled controls produce an unusable form.

The element that fixes this is old, unglamorous, and frequently replaced by a div for styling reasons. Its behaviour is worth understanding precisely, because the replacement loses something that is not obvious from the markup.

Two things a group carries that a label cannot

A group contributes a name and a role. The name is the group’s text: the legend. The role is group (or radiogroup when the contents are radio buttons), and the role is what makes the name behave differently from a field’s label.

The distinction shows up in how the name is delivered. A field’s accessible name is spoken every time focus enters the field. A group’s accessible name is spoken when focus enters the group from outside it, and not again when focus moves between members of the same group. Moving from the second radio button to the third announces the third button’s name and state, not the legend again. That is the correct behaviour: the user has already been told what the group is about, and repeating the legend on every arrow key would bury the option names.

The mechanism behind this is a boundary in the accessibility tree. The fieldset is exposed as a node with a name, and the controls are its children. Focus movement within a subtree does not re-announce the subtree’s name; focus entering the subtree does. This is why a div with a role="group" and an aria-label works as a substitute in principle, and why a plain div with a bold heading above it does not: the heading is a sibling, not a name.

How the legend is selected

The legend is not just any text inside the fieldset. The specification defines the fieldset’s name as the text of its first <legend> child element, and only that element. A second legend is ignored for naming. A legend placed inside a nested wrapper div rather than as a direct child is not used either, and neither is a <p> with the same visual styling.

The text taken is the legend’s full text content, including descendants. A legend containing a <span> with a required-field marker contributes that marker’s text to the group name. That has a consequence worth planning for: a visual asterisk rendered as a pseudo-element contributes nothing, because generated content is not part of the text content, while an asterisk rendered as a real character inside a <span> does contribute. Screen readers may or may not announce the character depending on the platform’s punctuation verbosity setting, so a group whose name is “Delivery method star” in some configurations and “Delivery method” in others is the usual outcome of marking the legend itself as required.

The legend is also rendered as a visible caption: the browser draws a border around the fieldset and interrupts it with the legend text. That default rendering is what pushes most designs away from the element, and it is fully replaceable. border: 0, padding: 0, and margin: 0 remove the frame; the grouping semantics survive, because they come from the element and not from the border. A fieldset with the browser’s frame removed is still a group with a name in the accessibility tree, which is the property that makes the element usable inside a designed layout.

Where the grouping actually lands

The name and the role travel through the platform accessibility API, and the shape they take differs by API. HTML-AAM defines the mapping per platform: the legend becomes the group’s accessible name, and depending on the platform the name may surface in a property that is spoken as a group label rather than as a name in the strict sense. Because the mapping is defined per platform rather than by the element, the same markup can produce slightly different speech in different screen readers, and the ordering of the legend relative to the first control’s announcement can vary. What is consistent is that the legend is announced once on entry rather than per control.

The children of a fieldset are affected in one other way: a disabled attribute on the fieldset disables every form control inside it, except the controls inside its first <legend>, and that disabled state propagates to the accessibility tree. This is the only grouping mechanism in HTML with that reach. Disabling a group of shipping options is a single attribute, and every radio button inside becomes unfocusable and announced as unavailable, rather than each one needing its own attribute.

Nesting is legal and occasionally right. A fieldset inside a fieldset produces a group inside a group, each with its own name, and the inner legend is announced when focus enters the inner group. The nesting depth is unbounded in the specification but not in practice: three levels of nested groups produce speech that is hard to follow, and two is usually the useful maximum.

The patterns a div cannot reproduce

Replacing a fieldset with a div and the legend with a heading is the common refactor, and it is not always wrong, but the substitution has to reproduce the name and the role, and the natural-looking fix usually reproduces neither.

<!-- No group name: the heading is a sibling of the controls -->
<div class="group">
  <h3>Delivery method</h3>
  <label><input type="radio" name="ship" value="std"> Standard</label>
  <label><input type="radio" name="ship" value="exp"> Express</label>
</div>

<!-- Group name present, role present -->
<fieldset>
  <legend>Delivery method</legend>
  <label><input type="radio" name="ship" value="std"> Standard</label>
  <label><input type="radio" name="ship" value="exp"> Express</label>
</fieldset>

The first version announces three things when tabbing in: a heading, then “Standard, radio button, not checked”. The heading is skippable and skippable things get skipped. The second announces “Delivery method, group” and then the first option.

A div can be made equivalent with an explicit role and an explicit name:

<div role="radiogroup" aria-labelledby="ship-heading">
  <h3 id="ship-heading">Delivery method</h3>
  ...
</div>

This works, and it has one cost the fieldset does not: the author is now responsible for the role matching the contents. A radiogroup containing checkboxes is a lie told to the accessibility tree, and the tree does not validate it. The native element derives its role from what it contains, so it cannot drift out of sync.

Limits and failure modes

Several properties of the element constrain how far it can be pushed.

A fieldset’s legend cannot be positioned freely without care. Because the legend is drawn as part of the fieldset’s border, it is the one element in HTML that participates in border rendering; absolute positioning it or applying float removes it from that role and, in some engines, from the accessible name computation as well. Designs that need the group title in an unusual place are better served by keeping the legend visually present and styling it, or by moving to the ARIA pattern and accepting the naming responsibility.

The element does not provide a description. There is no attribute that adds a group-level hint the way aria-describedby adds a field-level hint; a paragraph after the legend inside the fieldset is read as ordinary content in the group, not as part of the group’s name. Where a group needs both a name and a longer instruction, the instruction belongs in the group’s contents, positioned before the controls, or attached to the first control.

Fieldsets interact badly with implicit label association when a label wraps a fieldset. Nesting a fieldset inside a <label> does not make the fieldset a labelled control, and it can produce a name that concatenates the entire group text into one string, which is the opposite of the intent. Explicit for association, described in Label Association and the Accessible Name of a Form Control, is the only mechanism that survives here, and it does not apply to a fieldset at all.

Finally, the group name is not repeated in a form-wide error summary, and it is not carried into a field’s own error message. When a validation failure applies to a whole group, as with no delivery method selected, the message needs to name the group and point at it, which is a different problem from pointing at a single field. The mechanics of that are in Wiring Error Messages to Their Fields, and they depend on the group having a name to reference in the first place.

The element earns its place because it is the only construct in HTML where the shared question and the individual options are structurally connected. Every other approach is a reconstruction of that connection using attributes that must be kept in agreement by hand.

References

  1. HTML Living Standard: The fieldset element — WHATWG
  2. HTML Accessibility API Mappings 1.0 — W3C
  3. WAI-ARIA Authoring Practices Guide: Radio Group Pattern — W3C WAI