The fieldset Element
The <fieldset> element groups related form controls within a visual border. It is a block-level element and can contain any form controls: inputs, selects, textareas, and other fieldsets.
<form action="/order" method="POST">
<fieldset>
<legend>Personal information</legend>
<label for="full-name">Full name</label>
<input type="text" id="full-name" name="name" required>
<label for="email-pi">Email address</label>
<input type="email" id="email-pi" name="email" required>
</fieldset>
<button type="submit">Continue</button>
</form>
By default, browsers render a visible border around the fieldset and position the legend text overlapping the top border β a distinctive look that immediately signals a grouped section to sighted users.
The legend Element
The <legend> element must be the first child of a <fieldset>. It provides a caption or title for the group. A fieldset without a legend is allowed but leaves screen reader users without a group label.
<fieldset>
<legend>Payment details</legend>
<!-- form controls here -->
</fieldset>
<!-- Legend can contain HTML for richer styling -->
<fieldset>
<legend><strong>Billing address</strong> <span class="required-note">* required</span></legend>
<!-- form controls here -->
</fieldset>
Accessibility Benefits
The combination of <fieldset> and <legend> provides a crucial accessibility benefit: screen readers announce the legend text before the label of each field inside the group. This gives context that would otherwise be missing.
For example, a form with two groups of radio buttons β "Delivery method" and "Payment method" β without fieldsets would be announced as just "Standard" and "Express" with no context. With fieldsets, screen readers say "Delivery method group β Standard" and "Payment method group β Credit card".
<!-- β
Correct: radio group inside fieldset with legend -->
<fieldset>
<legend>Delivery method</legend>
<label><input type="radio" name="delivery" value="standard"> Standard (3β5 days)</label>
<label><input type="radio" name="delivery" value="express"> Express (next day)</label>
<label><input type="radio" name="delivery" value="same-day"> Same day</label>
</fieldset>
<!-- β
Correct: checkbox group inside fieldset with legend -->
<fieldset>
<legend>Notification preferences</legend>
<label><input type="checkbox" name="notify" value="email"> Email updates</label>
<label><input type="checkbox" name="notify" value="sms"> SMS alerts</label>
<label><input type="checkbox" name="notify" value="push"> Push notifications</label>
</fieldset>
Disabled fieldset
Adding the disabled attribute to a <fieldset> disables all form controls inside it with a single attribute. This is far more convenient than adding disabled to every individual input.
<form action="/checkout" method="POST">
<fieldset>
<legend>Shipping address</legend>
<label for="ship-line1">Address line 1</label>
<input type="text" id="ship-line1" name="ship_line1">
<label for="ship-city">City</label>
<input type="text" id="ship-city" name="ship_city">
<label for="ship-postcode">Postcode</label>
<input type="text" id="ship-postcode" name="ship_postcode">
</fieldset>
<!-- Disabled fieldset: all inputs inside are greyed out and non-interactive -->
<fieldset disabled id="billing-fieldset">
<legend>
Billing address
<label>
<input type="checkbox" id="same-as-shipping">
Same as shipping
</label>
</legend>
<label for="bill-line1">Address line 1</label>
<input type="text" id="bill-line1" name="bill_line1">
<label for="bill-city">City</label>
<input type="text" id="bill-city" name="bill_city">
</fieldset>
</form>
// Toggle billing fieldset based on checkbox
const checkbox = document.getElementById('same-as-shipping');
const billing = document.getElementById('billing-fieldset');
checkbox.addEventListener('change', function() {
billing.disabled = this.checked;
});
readonly on individual inputs instead.
Practical Example: Multi-Section Form
Here is a complete checkout form with three fieldsets for personal details, shipping, and payment.
<form action="/checkout/confirm" method="POST">
<fieldset>
<legend>Personal details</legend>
<div class="form-row">
<div class="form-group">
<label for="co-fname">First name *</label>
<input type="text" id="co-fname" name="first_name" required>
</div>
<div class="form-group">
<label for="co-lname">Last name *</label>
<input type="text" id="co-lname" name="last_name" required>
</div>
</div>
<div class="form-group">
<label for="co-email">Email address *</label>
<input type="email" id="co-email" name="email" required autocomplete="email">
</div>
</fieldset>
<fieldset>
<legend>Shipping address</legend>
<div class="form-group">
<label for="co-addr1">Address line 1 *</label>
<input type="text" id="co-addr1" name="address_1" required>
</div>
<div class="form-group">
<label for="co-addr2">Address line 2</label>
<input type="text" id="co-addr2" name="address_2">
</div>
<div class="form-row">
<div class="form-group">
<label for="co-city">City *</label>
<input type="text" id="co-city" name="city" required>
</div>
<div class="form-group">
<label for="co-post">Postcode *</label>
<input type="text" id="co-post" name="postcode" required>
</div>
</div>
</fieldset>
<fieldset>
<legend>Payment method</legend>
<label><input type="radio" name="payment" value="card" checked> Credit / Debit card</label>
<label><input type="radio" name="payment" value="paypal"> PayPal</label>
<label><input type="radio" name="payment" value="bank"> Bank transfer</label>
</fieldset>
<button type="submit">Place order</button>
</form>
Styling Fieldsets with CSS
The browser's default fieldset border and legend rendering can be completely overridden with CSS. A common pattern is to remove the default border and use custom dividers or card styles.
/* Remove default border and padding */
fieldset {
border: none;
padding: 0;
margin: 0 0 2rem;
}
/* Custom section divider style */
fieldset {
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
legend {
font-size: 1.1rem;
font-weight: 600;
color: #212529;
padding: 0 0.5rem;
}
/* Card-style fieldset (no visible border, just background) */
fieldset.card-section {
border: none;
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
}
fieldset.card-section legend {
background: #0d6efd;
color: #fff;
padding: 0.25rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
}
/* Disabled fieldset styling */
fieldset:disabled {
opacity: 0.5;
cursor: not-allowed;
}
fieldset and legend: Grouping Form Controls
<fieldset> groups related form controls, and <legend> gives that group a caption. Beyond the visual border, this pairing is important for accessibility β especially radio buttons.
<fieldset>
<legend>Shipping speed</legend> <!-- caption for the whole group -->
<label><input type="radio" name="ship" value="std"> Standard</label>
<label><input type="radio" name="ship" value="exp"> Express</label>
</fieldset>
| Element | Role |
|---|---|
<fieldset> | groups related controls |
<legend> | the group's label (first child) |
Why it matters for radio buttons: a single radio's <label> only says "Standard" or "Express" β it doesn't convey what the choice is about. Wrapping them in a <fieldset> with <legend>Shipping speed</legend> lets a screen reader announce "Shipping speed, Standard, radio button 1 of 2" β the group context that makes the choice understandable without sight. Without it, a blind user hears disconnected options. Details: the <legend> must be the first child of the <fieldset>. Use fieldsets to break long forms into logical sections (Billing, Shipping, Payment) β it helps everyone, not just screen-reader users. You can style away the default border with CSS while keeping the semantic grouping intact.
ποΈ Practical Exercise
- Wrap a group of related inputs in a
<fieldset>. - Add a
<legend>describing the group. - Create two fieldsets in one form (e.g. "Personal" and "Address").
- Disable an entire fieldset with the
disabledattribute. - Style the fieldset border with CSS.
π₯ Challenge Exercise
Build a registration form split into two <fieldset> groups β "Account Details" and "Contact Info" β each with a <legend>. Disable the second fieldset until needed. Explain how <fieldset> and <legend> improve accessibility for screen-reader users.
π Summary
<fieldset>groups related form controls visually and semantically; renders a border by default.<legend>must be the first child of fieldset; provides the group's accessible name.- Screen readers announce the legend before each control inside β essential context for radio and checkbox groups.
disabledon a fieldset disables all child inputs at once β a convenient shortcut, but disabled inputs are not submitted.- Use one fieldset per logical section (personal info, shipping address, payment) on long forms.
- Default browser styles can be completely replaced with CSS; a common approach is to remove the border and apply card or section styles.
Interview Questions
- What is the purpose of the
<fieldset>element? - What does
<legend>do? - How does grouping inputs help accessibility?
- What happens when you add
disabledto a<fieldset>? - Where must a
<legend>be placed inside a fieldset?
Related Topics
FAQ
No. Fieldset is great for any group of related fields β for example, a shipping address section (street, city, postcode, country), a "personal details" group, or a "payment details" group. While it is most commonly recommended for radio and checkbox groups (because it is the only semantic way to name those groups for screen readers), it adds value wherever you have logically related controls that benefit from a group label.
Yes, fieldsets can be nested. Each nested fieldset should have its own legend. However, deeply nested fieldsets are rare in practice. A common use case is an outer fieldset for "Addresses" containing two inner fieldsets for "Shipping address" and "Billing address".
Set border: none on the fieldset in CSS. You may also need to reset padding: 0 and margin: 0 since browsers apply default padding to fieldset elements. Use min-width: 0 if the fieldset is inside a flexbox container β fieldsets have a min-width: min-content quirk that can cause layout issues in flex/grid.
No. When a fieldset is disabled, all form controls inside it are effectively disabled β their names and values are excluded from the form submission entirely, just as if each individual input had the disabled attribute. If you need to submit the values but keep the fields non-editable, use readonly on each input instead (note that readonly is not available on select or button elements).

