Grouping Fields with <fieldset> and <legend>
Forms are often more than a flat list of inputs — a shipping form has an
address section and a payment section; a survey has groups of related
radio buttons. <fieldset> draws a semantic (and, by default, visual)
boundary around a group of related controls, and <legend> gives that
group a caption. Together they turn "just a bunch of inputs" into a
form with real structure.
Basic Usage
fieldset-basic.html
<form>
<fieldset>
<legend>Shipping 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" />
<label for="zip">ZIP code</label>
<input type="text" id="zip" name="zip" />
</fieldset>
<fieldset>
<legend>Payment Details</legend>
<label for="card-number">Card number</label>
<input type="text" id="card-number" name="card-number" />
</fieldset>
</form><legend> must be the first child inside its <fieldset> for browsers and assistive technology to treat it as the group's caption. Anything else goes after it.The Classic Use Case: Radio & Checkbox Groups
The single most useful pattern for <fieldset>/<legend> is grouping a
set of radio buttons or checkboxes that represent one logical question.
Without it, a screen reader announces each option's label individually with
no shared context — the visitor has no idea what the choice is for.
fieldset-radio-group.html
<fieldset> <legend>Preferred contact method</legend> <input type="radio" id="contact-email" name="contact" value="email" /> <label for="contact-email">Email</label> <input type="radio" id="contact-phone" name="contact" value="phone" /> <label for="contact-phone">Phone</label> <input type="radio" id="contact-sms" name="contact" value="sms" /> <label for="contact-sms">SMS</label> </fieldset>
Why It Matters for Accessibility
Each individual <label> in the group above announces its
own text ("Email", "Phone", "SMS") — but none of them say what question is
being answered. When the group sits inside a <fieldset> with a
<legend>, most screen readers announce the legend text together with
each option, effectively reading "Preferred contact method, Email" for the
first radio button. That context is easy to lose without it.
One
<fieldset>per logical question or grouping — not one per field.The
<legend>text should describe the group as a whole, not repeat an individual option.Nested
<fieldset>s are allowed for sub-groups within a bigger group (e.g. billing address inside a payment fieldset).
Disabling an Entire Group
Setting disabled directly on a <fieldset> disables every form
control nested inside it in one shot — a much simpler alternative to
disabling each input individually with JavaScript.
fieldset-disabled.html
<form>
<fieldset id="billing-fields" disabled>
<legend>Billing Address</legend>
<label for="same-as-shipping">
<input type="checkbox" id="same-as-shipping" checked />
Same as shipping address
</label>
<label for="bill-street">Street</label>
<input type="text" id="bill-street" name="bill-street" />
</fieldset>
</form>
<script>
const checkbox = document.getElementById('same-as-shipping');
const fieldset = document.getElementById('billing-fields');
checkbox.addEventListener('change', () => {
fieldset.disabled = checkbox.checked;
});
</script><fieldset> are excluded from form submission entirely — their values won't appear in the submitted data, even if the input itself has a value. Don't use this to "hide" data you still need sent to the server.Styling <fieldset> and <legend>
By default, browsers render <fieldset> with a border and inner padding,
and <legend> overlapping that border. Both are fully styleable with CSS —
a very common pattern is removing the border entirely and using the
fieldset purely for its semantic/accessibility value.
fieldset.css
fieldset {
border: none;
padding: 0;
margin: 0 0 24px;
}
legend {
font-weight: 600;
margin-bottom: 8px;
padding: 0;
}<fieldset> with a <legend> describing the shared question.