# Fieldset

> Group related form controls under one visible question or label.

Source: https://www.honestui.com/docs/components/fieldset

```tsx
import {
  Field,
  FieldControl,
  FieldDescription,
  FieldLabel,
} from "@/components/honest-ui/ui/field"
import { Fieldset, FieldsetLegend } from "@/components/honest-ui/ui/fieldset"

export function FieldsetDemo() {
  return (
    <Fieldset>
      <FieldsetLegend>Billing Details</FieldsetLegend>
      <Field>
        <FieldLabel>Company</FieldLabel>
        <FieldControl type="text" placeholder="Enter company name" />
        <FieldDescription>
          The name that will appear on invoices.
        </FieldDescription>
      </Field>

      <Field>
        <FieldLabel>Tax ID</FieldLabel>
        <FieldControl
          type="text"
          placeholder="Enter tax identification number"
        />
        <FieldDescription>
          Your business tax identification number.
        </FieldDescription>
      </Field>
    </Fieldset>
  )
}

```

## Overview [#overview]

Use Fieldset to group related form controls under one legend. A fieldset is useful for radio groups, checkbox groups, address sections, permission groups, and any form area where several inputs answer one larger question.

The root renders a real `<fieldset>` element with the browser's default border, padding, and margins removed, so you get the native grouping semantics without fighting the default chrome. Setting `disabled` on the root disables every field inside it in one place.

## Anatomy [#anatomy]

A fieldset has a container, legend, optional description, and controls. The legend names the group. The controls inside should all relate to that group.

`FieldsetLegend` renders a styled `div` rather than a native `<legend>` element. Its generated id is applied as `aria-labelledby` on the fieldset, which is what gives the group its accessible name — and because it is a normal block element inside a flex column, it participates in layout predictably instead of obeying `<legend>`'s unusual rendering rules.

## Grouping decisions [#grouping-decisions]

Use Field for one control. Use Fieldset for a set of related controls that answer one question. Combine both when each control in the group also needs its own label, description, or error — the fieldset names the question, each field names its part.

If fields only need visual spacing and do not form one semantic question, use `FieldGroup` from [Field](/docs/components/field) instead. Adding a legend to purely visual structure makes screen readers announce a group boundary that does not mean anything.

## Accessibility [#accessibility]

Screen readers announce the fieldset as a named group: the legend's id is wired to the fieldset through `aria-labelledby`, so controls inside are heard in context ("Notifications, checkbox, Product updates…"). Keep the legend visible and specific — an invisible or generic name helps no one.

Fields inside keep their own labels, descriptions, and errors; the legend supplements them, it does not replace them. `disabled` on the root propagates to every field, so assistive technology and the visual dimming agree about what is interactive.

Grouping is semantic, not focusable: keyboard users tab straight to the controls, so a fieldset adds context without adding keystrokes. Colors come from theme tokens, long legends wrap rather than overflow, and the flex-column layout mirrors cleanly in right-to-left locales.

## Installation [#installation]


  

  
    <CliBlock commands="[&#x22;fieldset&#x22;]" />
  

  
    
      
        Install the following dependencies:
      

      ```bash
      npm install @base-ui-components/react
      ```

      
        Copy and paste the following code into your project.
      

      ### components/ui/fieldset.tsx

```tsx
"use client"

import { Fieldset as FieldsetPrimitive } from "@base-ui-components/react/fieldset"

import { cn } from "@/lib/utils"

function Fieldset({ className, ...props }: FieldsetPrimitive.Root.Props) {
  return (
    <FieldsetPrimitive.Root
      data-slot="fieldset"
      className={cn(
        "m-0 flex flex-col gap-[var(--hui-space-4)] border-0 p-0",
        className
      )}
      {...props}
    />
  )
}
function FieldsetLegend({
  className,
  ...props
}: FieldsetPrimitive.Legend.Props) {
  return (
    <FieldsetPrimitive.Legend
      data-slot="fieldset-legend"
      className={cn(
        "mb-[var(--hui-space-2)] p-0 text-[var(--hui-color-foreground-base-primary)] [font-size:var(--hui-font-size-base)] [font-weight:var(--hui-font-weight-semibold)] [letter-spacing:var(--hui-letter-spacing-base)] [line-height:var(--hui-line-height-base)]",
        className
      )}
      {...props}
    />
  )
}

export { Fieldset, FieldsetLegend }

```

      
        Update the import paths to match your project setup.
      
    
  


## Usage [#usage]

```tsx
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
```

```tsx
<Fieldset>
  <FieldsetLegend>Notification channels</FieldsetLegend>
  {/* Related Fields go here */}
</Fieldset>
```

## Don't do this [#dont-do-this]

### Vague legends [#vague-legends]

```tsx
// Bad
<Fieldset>
  <FieldsetLegend>Details</FieldsetLegend>
  <Field>{/* Phone */}</Field>
  <Field>{/* Address */}</Field>
</Fieldset>
```

```tsx
// Good
<Fieldset>
  <FieldsetLegend>Contact information</FieldsetLegend>
  <Field>{/* Phone */}</Field>
  <Field>{/* Address */}</Field>
</Fieldset>
```

The legend is announced before every control inside the group — "Details, textbox, Phone" tells someone nothing about what question they are answering. Name the shared question, like "Contact information" or "Billing address", so context survives even when someone jumps directly to the third control.

### One legend doing the labels' job [#one-legend-doing-the-labels-job]

```tsx
// Bad
<Fieldset>
  <FieldsetLegend>Enter your shipping address, then your billing address if different</FieldsetLegend>
  <FieldControl name="shipping" />
  <FieldControl name="billing" />
</Fieldset>
```

```tsx
// Good
<Fieldset>
  <FieldsetLegend>Shipping details</FieldsetLegend>
  <Field>
    <FieldLabel>Street address</FieldLabel>
    <FieldControl name="shipping" />
  </Field>
  <Field>
    <FieldLabel>Billing street address</FieldLabel>
    <FieldControl name="billing" />
  </Field>
</Fieldset>
```

Instructions crammed into a legend do not become each control's accessible name. Screen reader users landing on the second input hear only "Billing details, edit text" with no hint about what belongs there. Every control gets its own label; the legend stays short enough to serve as context, not instructions.

## Examples [#examples]

### Notification settings [#notification-settings]

One legend provides context for several related preferences, each with its own label.

```tsx
import { Checkbox } from "@/components/honest-ui/ui/checkbox"
import { Field, FieldLabel } from "@/components/honest-ui/ui/field"
import { Fieldset, FieldsetLegend } from "@/components/honest-ui/ui/fieldset"

export function FieldsetNotificationSettings() {
  return (
    <Fieldset className="max-w-sm rounded-xl border p-4">
      <FieldsetLegend>Notifications</FieldsetLegend>
      <Field><FieldLabel><Checkbox defaultChecked /> Product updates</FieldLabel></Field>
      <Field><FieldLabel><Checkbox /> Weekly digest</FieldLabel></Field>
      <Field><FieldLabel><Checkbox defaultChecked /> Security alerts</FieldLabel></Field>
    </Fieldset>
  )
}

```

### Billing address [#billing-address]

Group address fields that together form one larger answer.

```tsx
import { Field, FieldControl, FieldLabel } from "@/components/honest-ui/ui/field"
import { Fieldset, FieldsetLegend } from "@/components/honest-ui/ui/fieldset"

export function FieldsetBillingAddress() {
  return (
    <Fieldset className="max-w-sm rounded-xl border p-4">
      <FieldsetLegend>Billing address</FieldsetLegend>
      <Field><FieldLabel>Company</FieldLabel><FieldControl defaultValue="Acme Inc." /></Field>
      <Field><FieldLabel>ZIP code</FieldLabel><FieldControl defaultValue="94107" /></Field>
    </Fieldset>
  )
}

```

### Danger zone [#danger-zone]

Clear structure and consequences around high-impact settings.

```tsx
import { Switch } from "@/components/honest-ui/ui/switch"
import { Field, FieldDescription, FieldLabel } from "@/components/honest-ui/ui/field"
import { Fieldset, FieldsetLegend } from "@/components/honest-ui/ui/fieldset"

export function FieldsetDangerZone() {
  return (
    <Fieldset className="max-w-sm rounded-xl border border-destructive/30 p-4">
      <FieldsetLegend>Danger zone</FieldsetLegend>
      <Field>
        <FieldLabel className="justify-between w-full">Require delete confirmation <Switch /></FieldLabel>
        <FieldDescription>Ask for a typed confirmation before destructive actions.</FieldDescription>
      </Field>
    </Fieldset>
  )
}

```

### Disabling a whole group [#disabling-a-whole-group]

`disabled` on the root reaches every field inside, useful when an entire section depends on another setting.

<ComponentSource name="fieldset-disabled" title="examples/fieldset-disabled.tsx" />

## API reference [#api-reference]

| Part             | Renders      | Props                                                                                              |
| ---------------- | ------------ | -------------------------------------------------------------------------------------------------- |
| `Fieldset`       | `<fieldset>` | Native fieldset props plus `disabled` (boolean, default `false`), which disables all fields inside |
| `FieldsetLegend` | `<div>`      | Native div props; its generated id is applied as the fieldset's `aria-labelledby`                  |

Both parts forward Base UI's `render` composition prop. The root strips the browser's default fieldset border, padding, and margins and lays its children out as a column.

See the [Base UI Fieldset API](https://base-ui.com/react/components/fieldset#api-reference).
