# Fieldset (/docs/fieldset)





## API Reference [#api-reference]

<BaseUIReference href="https://base-ui.com/react/components/fieldset" />

## Basic [#basic]

<Preview cssProperties="fieldsetPlaygroundCssProperties">
  <FieldsetExample />

  <Preview.Code>
    {`
          import {
            Fieldset,
            FieldsetLegend,
            Field,
            FieldLabel,
            FieldControl,
            FieldError,
          } from "moduix";

          export function FieldsetDemo() {
            return (
              <Fieldset>
                <FieldsetLegend>Billing details</FieldsetLegend>

                <Field validationMode="onBlur">
                  <FieldLabel>Company</FieldLabel>
                  <FieldControl required placeholder="Enter company name" />
                  <FieldError match="valueMissing">Please enter company name.</FieldError>
                </Field>

                <Field validationMode="onBlur">
                  <FieldLabel>Tax ID</FieldLabel>
                  <FieldControl required placeholder="Enter tax ID" />
                  <FieldError match="valueMissing">Please enter tax ID.</FieldError>
                </Field>
              </Fieldset>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSSProperties>
    {(context) => <FieldsetCssPropertiesPanel {...context} />}
  </Preview.CSSProperties>

  <Preview.CSSPlayground>
    {(context) => <FieldsetCssPlaygroundPanel {...context} />}
  </Preview.CSSPlayground>
</Preview>

## Anatomy [#anatomy]

`Fieldset` groups related controls under one semantic legend. Keep the visible group label in
`FieldsetLegend`, then place related `Field` blocks (or composed rows like radio options) inside the
same root so native fieldset semantics and disabled behavior stay consistent.

```text
Fieldset
├─ FieldsetLegend
└─ grouped content
   ├─ Field
   │  ├─ FieldLabel
   │  ├─ FieldControl
   │  └─ FieldError
   └─ Field / FieldItem rows (optional)
```

```tsx
<Fieldset>
  <FieldsetLegend>Billing details</FieldsetLegend>

  <Field validationMode="onBlur">
    <FieldLabel>Company</FieldLabel>
    <FieldControl required placeholder="Enter company name" />
    <FieldError match="valueMissing">Please enter company name.</FieldError>
  </Field>
</Fieldset>
```

| Part             | Role                                                                                                   |
| ---------------- | ------------------------------------------------------------------------------------------------------ |
| `Fieldset`       | Root semantic group. Renders native `fieldset`, controls shared disabled state, and supports `render`. |
| `FieldsetLegend` | Group label announced by assistive technologies for all nested controls in the fieldset.               |

`Fieldset` does not have service slots such as `portal`, `backdrop`, or `viewport`.
In most cases, keep default semantic structure and customize visible parts (`Fieldset`,
`FieldsetLegend`, and nested field slots) with `className` and CSS variables.

## Composition [#composition]

`Fieldset` renders a native `fieldset` by default and forwards Base UI state, refs, `className`,
function `className`, `style`, function `style`, and `render`. Use `render` when the same root
needs to also be another Base UI component, such as `RadioGroup`.

`Fieldset` and `FieldsetLegend` are the only public parts. There are no hidden positioner,
backdrop, viewport, portal, or other service slots to style through `classNames`; pass `className`
directly to the part you want to customize.

```tsx
<Fieldset className={styles.fieldset}>
  <FieldsetLegend className={styles.legend}>Billing details</FieldsetLegend>
</Fieldset>
```

The package exports Base UI state types for advanced styling callbacks:

```tsx
import { type FieldsetState, type FieldsetLegendState } from 'moduix';
```

## Examples [#examples]

### Disabled [#disabled]

Use `disabled` on `Fieldset` to prevent interaction with every native control inside the group.

<Preview>
  <DisabledFieldsetExample />

  <Preview.Code>
    {`
          import {
            Fieldset,
            FieldsetLegend,
            Field,
            FieldLabel,
            FieldControl,
          } from "moduix";

          export function DisabledFieldsetDemo() {
            return (
              <Fieldset disabled>
                <FieldsetLegend>Disabled account details</FieldsetLegend>

                <Field>
                  <FieldLabel>Email</FieldLabel>
                  <FieldControl defaultValue="team@example.com" />
                </Field>

                <Field>
                  <FieldLabel>Phone</FieldLabel>
                  <FieldControl defaultValue="+1 (555) 123-45-67" />
                </Field>
              </Fieldset>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Radio Group [#radio-group]

Use `render` to compose the fieldset semantics with another root component, such as `RadioGroup`.

<Preview>
  <RadioGroupFieldsetExample />

  <Preview.Code>
    {`
          import {
            Field,
            Fieldset,
            RadioGroup,
            FieldsetLegend,
            FieldItem,
            FieldLabel,
            Radio,
            RadioLabel,
          } from "moduix";

          export function RadioGroupFieldsetDemo() {
            return (
              <Field name="storageType">
                <Fieldset render={<RadioGroup defaultValue="ssd" />}>
                  <FieldsetLegend>Storage type</FieldsetLegend>

                  {storageTypes.map((item) => (
                    <FieldItem key={item.value}>
                      <FieldLabel>
                        <Radio value={item.value} />
                        <RadioLabel>{item.label}</RadioLabel>
                      </FieldLabel>
                    </FieldItem>
                  ))}
                </Fieldset>
              </Field>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          const storageTypes = [
            { value: "ssd", label: "SSD" },
            { value: "hdd", label: "HDD" },
          ];
        `}
  </Preview.Data>
</Preview>

### Custom Styles [#custom-styles]

Pass `className` to `Fieldset`, `FieldsetLegend`, and nested field slots when styling with CSS Modules, Tailwind CSS, or CSS-in-JS.

<Preview>
  <CustomStylesFieldsetExample />

  <Preview.Code>
    {`
          import {
            Fieldset,
            FieldsetLegend,
            Field,
            FieldLabel,
            FieldControl,
            FieldError,
          } from "moduix";

          export function StyledFieldsetDemo() {
            return (
              <Fieldset className={styles.customFieldset}>
                <FieldsetLegend className={styles.customLegend}>
                  Styled fieldset
                </FieldsetLegend>

                <Field validationMode="onBlur" className={styles.customField}>
                  <FieldLabel className={styles.customLabel}>Project name</FieldLabel>
                  <FieldControl
                    required
                    placeholder="Maps Platform"
                    className={styles.customControl}
                  />
                  <FieldError className={styles.customError} match="valueMissing">
                    Please enter a project name.
                  </FieldError>
                </Field>
              </Fieldset>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .customFieldset {
            max-width: 20rem;
            gap: var(--spacing-3);
            padding: var(--spacing-4);
            border: var(--border-width-sm) solid color-mix(in srgb, var(--color-primary) 30%, transparent);
            border-radius: var(--radius-lg);
          }

          .customLegend {
            border-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
            color: var(--color-primary);
          }

          .customField {
            gap: var(--spacing-2);
          }

          .customLabel,
          .customError {
            color: var(--color-primary);
          }

          .customControl {
            border-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
          }

          .customControl:focus {
            outline-color: var(--color-primary);
          }
        `}
  </Preview.CSS>
</Preview>
