Fieldset
A native fieldset element with a legend. Built with Base UI and Tailwind CSS. Copy-paste ready.
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
import { Input } from "@/components/ui/input";
export default function Particle() {
return (
<Fieldset className="flex w-full flex-col gap-6">
<FieldsetLegend>Billing Details</FieldsetLegend>
<Field>
<FieldLabel>Company</FieldLabel>
<Input placeholder="Enter company name" type="text" />
<FieldDescription>
The name that will appear on invoices.
</FieldDescription>
</Field>
<Field>
<FieldLabel>Tax ID</FieldLabel>
<Input placeholder="Enter tax identification number" type="text" />
<FieldDescription>
Your business tax identification number.
</FieldDescription>
</Field>
</Fieldset>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/fieldset
Usage
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset"<Fieldset>
<FieldsetLegend>Fieldset legend</FieldsetLegend>
</Fieldset>Notes
- When to use — wrap a group of related inputs (radio buttons, checkboxes, or multiple fields for the same concept) inside a
Fieldset. TheFieldsetLegendnames the group for screen readers. - Accessibility — screen readers announce the legend text before reading each input inside the fieldset, giving users context for each field without extra labels.
- Disabled state — setting
disabledon<Fieldset>disables all interactive elements inside it at the browser level. No JavaScript needed.
Examples
Personal Information
A fieldset grouping first name, last name, email, and optional phone fields — a typical account creation or profile section.
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
import { Input } from "@/components/ui/input";
export default function Particle() {
return (
<Fieldset className="flex w-full flex-col gap-6">
<FieldsetLegend>Personal Information</FieldsetLegend>
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel>First name</FieldLabel>
<Input placeholder="Jane" type="text" />
</Field>
<Field>
<FieldLabel>Last name</FieldLabel>
<Input placeholder="Smith" type="text" />
</Field>
</div>
<Field>
<FieldLabel>Email address</FieldLabel>
<Input placeholder="jane@example.com" type="email" />
<FieldDescription>
We'll send account-related emails here.
</FieldDescription>
</Field>
<Field>
<FieldLabel>Phone number</FieldLabel>
<Input placeholder="+1 (555) 000-0000" type="tel" />
<FieldDescription>
Optional. Used for SMS notifications.
</FieldDescription>
</Field>
</Fieldset>
);
}
Shipping Address
Street, city, ZIP, and country fields grouped under a "Shipping Address" legend, with required indicators and inline error messages.
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
import { Input } from "@/components/ui/input";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const countries = [
{ label: "Select a country", value: null },
{ label: "United States", value: "us" },
{ label: "United Kingdom", value: "uk" },
{ label: "Canada", value: "ca" },
{ label: "Australia", value: "au" },
{ label: "Germany", value: "de" },
];
export default function Particle() {
return (
<Fieldset className="flex w-full flex-col gap-4">
<FieldsetLegend>Shipping Address</FieldsetLegend>
<Field>
<FieldLabel>
Street address <span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input placeholder="123 Main St" required type="text" />
<FieldError>Street address is required.</FieldError>
</Field>
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel>
City <span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input placeholder="San Francisco" required type="text" />
<FieldError>City is required.</FieldError>
</Field>
<Field>
<FieldLabel>
ZIP / Postal code{" "}
<span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input placeholder="94103" required type="text" />
<FieldError>ZIP code is required.</FieldError>
</Field>
</div>
<Field>
<FieldLabel>Country</FieldLabel>
<Select items={countries}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{countries.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
</Field>
</Fieldset>
);
}
Notification Preferences
Three toggle rows — email, push, and marketing notifications — each with a label and description, inside a shared fieldset for semantic grouping.
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
import { Switch } from "@/components/ui/switch";
export default function Particle() {
return (
<Fieldset className="flex w-full max-w-sm flex-col gap-1">
<FieldsetLegend className="mb-3">Notification Preferences</FieldsetLegend>
<Field className="flex-row items-center justify-between rounded-lg border p-3">
<div className="flex flex-col gap-0.5">
<FieldLabel className="cursor-pointer text-sm">
Email notifications
</FieldLabel>
<FieldDescription className="text-xs">
Receive updates and alerts by email.
</FieldDescription>
</div>
<Switch defaultChecked />
</Field>
<Field className="flex-row items-center justify-between rounded-lg border p-3">
<div className="flex flex-col gap-0.5">
<FieldLabel className="cursor-pointer text-sm">
Push notifications
</FieldLabel>
<FieldDescription className="text-xs">
Get real-time alerts on your device.
</FieldDescription>
</div>
<Switch />
</Field>
<Field className="flex-row items-center justify-between rounded-lg border p-3">
<div className="flex flex-col gap-0.5">
<FieldLabel className="cursor-pointer text-sm">
Marketing emails
</FieldLabel>
<FieldDescription className="text-xs">
News, promotions, and product updates.
</FieldDescription>
</div>
<Switch />
</Field>
</Fieldset>
);
}
Change Password
Current and new password fields plus a confirm step, grouped under a "Change Password" legend with per-field validation errors.
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
import { Input } from "@/components/ui/input";
export default function Particle() {
return (
<Fieldset className="flex w-full flex-col gap-5">
<FieldsetLegend>Change Password</FieldsetLegend>
<Field>
<FieldLabel>Current password</FieldLabel>
<Input
autoComplete="current-password"
placeholder="Enter current password"
required
type="password"
/>
<FieldError>Current password is required.</FieldError>
</Field>
<Field>
<FieldLabel>
New password <span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input
autoComplete="new-password"
minLength={8}
placeholder="Enter new password"
required
type="password"
/>
<FieldDescription>At least 8 characters.</FieldDescription>
<FieldError>Password must be at least 8 characters.</FieldError>
</Field>
<Field>
<FieldLabel>
Confirm new password{" "}
<span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input
autoComplete="new-password"
placeholder="Re-enter new password"
required
type="password"
/>
<FieldError>Passwords do not match.</FieldError>
</Field>
</Fieldset>
);
}
Payment Information
Cardholder name, card number, expiry date, and CVC laid out in a responsive grid, with autocomplete hints and required-field validation.
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
import { Input } from "@/components/ui/input";
export default function Particle() {
return (
<Fieldset className="flex w-full flex-col gap-5">
<FieldsetLegend>Payment Information</FieldsetLegend>
<Field>
<FieldLabel>
Cardholder name <span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input
autoComplete="cc-name"
placeholder="Jane Smith"
required
type="text"
/>
<FieldError>Cardholder name is required.</FieldError>
</Field>
<Field>
<FieldLabel>
Card number <span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input
autoComplete="cc-number"
inputMode="numeric"
placeholder="1234 5678 9012 3456"
required
type="text"
/>
<FieldError>Please enter a valid card number.</FieldError>
</Field>
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel>
Expiry date <span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input
autoComplete="cc-exp"
placeholder="MM / YY"
required
type="text"
/>
<FieldError>Invalid expiry date.</FieldError>
</Field>
<Field>
<FieldLabel>
CVC <span className="text-destructive-foreground">*</span>
</FieldLabel>
<Input
autoComplete="cc-csc"
inputMode="numeric"
placeholder="123"
required
type="text"
/>
<FieldDescription>3 or 4 digits on your card.</FieldDescription>
<FieldError>Invalid CVC.</FieldError>
</Field>
</div>
</Fieldset>
);
}

