HTML fieldset and legend

When a form has more than a handful of fields, throwing every input onto the page as one long list makes it hard for users to understand how the fields relate to each other. The <fieldset> element solves this by drawing a semantic and visual boundary around a group of related controls, and its companion <legend> element gives that group a caption. Together they turn a flat form into a structured, readable one — and they carry real accessibility meaning, not just visual styling.

Overview / How it works

<fieldset> is a block-level container that lives inside a <form> (though it can technically appear outside one). It groups related form controls — text inputs, checkboxes, radio buttons, selects, and so on — into a single logical unit in the DOM tree. Browsers render a <fieldset> with a default border and inner padding, visually boxing its contents, though this default appearance is just user-agent styling and can be overridden with CSS.

The <legend> element must be the first child of a <fieldset> (immediately after the opening tag). When present, the browser renders it as a caption embedded in the fieldset’s top border, visually breaking the border line to display the text. This is not just cosmetic: assistive technologies use the legend as the accessible name for the whole group. When a screen reader user tabs into a radio button inside a fieldset, it typically announces the legend text along with the individual option label, so the user hears something like “Shipping method, group; Standard, radio button” instead of just “Standard, radio button” with no context.

Semantically, a fieldset creates a grouping node in the accessibility tree (exposed with an implicit ARIA role of group, or radiogroup when every child is part of one radio button set). This is why fieldset/legend is the standard way to label a set of radio buttons or checkboxes that share a single question — a single <label> can describe one control, but only a fieldset’s legend can describe the group as a whole.

A <fieldset> also has a special behavior: adding the disabled attribute disables every form control nested inside it in one shot (except those inside a nested first <legend>, which are never disabled by the parent). This is useful for disabling an entire section of a form — for example, greying out billing address fields until the user unchecks “same as shipping.”

Syntax

<fieldset name="group-name" disabled form="form-id">
  <legend>Caption text</legend>
  <!-- form controls go here -->
</fieldset>
Part Description
<fieldset> The grouping container. Renders a border and padding by default; establishes a labelled group in the accessibility tree.
<legend> Must be the first child of <fieldset>. Provides the caption/accessible name for the group. Optional but strongly recommended whenever a fieldset is used.
name Optional attribute identifying the fieldset itself (not submitted as form data, since a fieldset has no value).
disabled Boolean attribute. When present, disables every form control nested inside the fieldset.
form Associates the fieldset with a <form> elsewhere in the document by its id, useful when the fieldset is not a descendant of that form.

Examples

Example 1: A basic grouped section

<form>
  <fieldset>
    <legend>Personal Information</legend>
    <label for="fname">First name:</label>
    <input type="text" id="fname" name="fname">
    <br>
    <label for="lname">Last name:</label>
    <input type="text" id="lname" name="lname">
  </fieldset>
</form>

Result: A bordered box appears with the words “Personal Information” embedded in the top edge of the border. Inside the box, two labeled text inputs (First name, Last name) are stacked on separate lines. The border visually communicates that these two fields belong together.

This is the simplest use case: no special behavior, just visual and semantic grouping of two related text fields under one caption.

Example 2: Grouping a radio button set

<form>
  <fieldset>
    <legend>Preferred contact method</legend>
    <input type="radio" id="contact-email" name="contact" value="email" checked>
    <label for="contact-email">Email</label>
    <br>
    <input type="radio" id="contact-phone" name="contact" value="phone">
    <label for="contact-phone">Phone</label>
    <br>
    <input type="radio" id="contact-mail" name="contact" value="mail">
    <label for="contact-mail">Postal mail</label>
  </fieldset>
</form>

Result: A bordered box captioned “Preferred contact method” contains three radio buttons stacked vertically, each with its own label (Email, Phone, Postal mail), with Email pre-selected.

This is the textbook use case for fieldset and legend. All three radios share the name="contact", so only one can be selected at a time, but visually and semantically nothing previously told the user what question the radios were answering. The legend supplies that question, and screen readers announce “Preferred contact method” as each radio receives focus.

Example 3: Disabling an entire section

<form>
  <fieldset>
    <legend>Shipping Address</legend>
    <label for="ship-street">Street:</label>
    <input type="text" id="ship-street" name="ship-street">
  </fieldset>

  <fieldset disabled>
    <legend>Billing Address (same as shipping)</legend>
    <label for="bill-street">Street:</label>
    <input type="text" id="bill-street" name="bill-street" value="Same as shipping">
  </fieldset>
</form>

Result: Two bordered sections appear stacked vertically. The first, “Shipping Address,” has an editable, normal-looking text input. The second, “Billing Address (same as shipping),” shows the same layout but its text input is greyed out and cannot be typed into or focused, because the disabled attribute on its fieldset cascades to every control inside it.

This shows the fieldset’s group-level disabled behavior, which saves you from adding disabled to every single input individually and keeps the form’s disabled/enabled state easy to toggle with one attribute (commonly done with JavaScript, though the toggle logic itself belongs to a JS course, not this one).

How it works step by step

  1. The HTML parser encounters <fieldset> and creates a fieldset DOM node as a child of whatever form (or standalone container) it’s nested in.
  2. If the next token is a <legend> start tag, the parser creates a legend node as the first child of the fieldset. A legend appearing anywhere else inside the fieldset is still valid DOM-wise but only the first-child legend gets the special rendering and accessible-name treatment; browsers are only required to treat the first legend specially.
  3. All subsequent child elements (inputs, labels, other block content) become children of the fieldset in document order.
  4. During layout, the browser’s default stylesheet gives the fieldset a border, margin, and padding, and reserves space in the top border for the legend’s text so the border appears to be “broken” around the caption.
  5. During accessibility-tree construction, the browser computes the fieldset’s accessible name from the legend’s text content and exposes the fieldset as a group (or radiogroup) node, which assistive technology announces when the user’s focus enters any control inside it.
  6. If disabled is present on the fieldset, the browser walks its descendant form controls and applies the disabled state to each one that supports it, excluding controls that happen to live inside a nested legend.

Common Mistakes

Mistake 1: Placing the legend anywhere but first

<fieldset>
  <label for="q">Your answer:</label>
  <input type="text" id="q" name="q">
  <legend>Question 1</legend>
</fieldset>

This markup is not malformed, but it defeats the purpose of the legend: many browsers and assistive technologies only give special caption treatment and accessible-name computation to a legend that is the fieldset’s first child. Placed later, it’s just an oddly-positioned block of text with no guaranteed relationship to the group. Fix it by moving the legend to immediately follow the opening <fieldset> tag:

<fieldset>
  <legend>Question 1</legend>
  <label for="q">Your answer:</label>
  <input type="text" id="q" name="q">
</fieldset>

Mistake 2: Using fieldset purely for visual boxes, with no legend

<fieldset>
  <input type="checkbox" id="newsletter" name="newsletter">
  <label for="newsletter">Subscribe to newsletter</label>
</fieldset>

This is valid HTML, but it wastes the fieldset’s purpose. If you only want a bordered box around a single unrelated control for visual effect, that’s a styling concern that belongs to CSS on a <div>, not a semantic fieldset with no caption. Reserve <fieldset>/<legend> for cases where you actually have a group of related controls that benefits from a shared, announced label:

<fieldset>
  <legend>Email preferences</legend>
  <input type="checkbox" id="newsletter" name="newsletter">
  <label for="newsletter">Subscribe to newsletter</label>
  <br>
  <input type="checkbox" id="promos" name="promos">
  <label for="promos">Receive promotional offers</label>
</fieldset>

Best Practices

  • Always pair a <fieldset> with a <legend> as its first child — a fieldset without a legend loses its main accessibility benefit.
  • Use fieldset/legend specifically for groups of related controls, especially radio button sets and checkbox groups that answer one shared question.
  • Keep legend text short and descriptive, since it will be announced repeatedly as users navigate between controls in the group.
  • Don’t nest a fieldset’s controls so deeply that the relationship between the legend and its controls becomes unclear; keep the group focused on one topic.
  • Use the disabled attribute on a fieldset when you need to disable a whole section together, rather than disabling each control individually.
  • Reserve visual borders and spacing tweaks for CSS; don’t rely on the default fieldset border as your only design choice if it doesn’t fit your site’s look.
  • Do not use fieldset purely as a generic layout box — if there’s no meaningful group of controls, a semantic <div> or plain markup is more appropriate.

Practice Exercises

  1. Build a form with a fieldset captioned “Account Type” containing three radio buttons: Free, Pro, and Enterprise. Make sure the legend is the first child of the fieldset.
  2. Create a fieldset captioned “Notification Settings” containing two checkboxes (“Email me updates” and “Text me updates”). Then add the disabled attribute to the fieldset and confirm (by reasoning through the rendering) that both checkboxes become non-interactive.
  3. Take a flat form with six unrelated-looking inputs (name, email, street, city, card number, expiry) and reorganize it into two fieldsets: one captioned “Contact Information” and one captioned “Payment Details,” grouping the relevant fields under each.

Summary

  • <fieldset> groups related form controls both visually (a default border) and semantically (a group node in the accessibility tree).
  • <legend> must be the first child of a fieldset and supplies its caption and accessible name.
  • Fieldset/legend is the standard way to label a set of radio buttons or checkboxes that share one question.
  • The disabled attribute on a fieldset disables every nested form control at once.
  • Fieldset styling (borders, spacing) is CSS’s job; don’t rely on default browser styling if it doesn’t match your design.
  • Only use fieldset/legend where there’s a genuine group of related controls — not as a generic decorative box.