Fieldset

Groups related controls under a shared legend.

Personal information
import * as stylex from "@stylexjs/stylex"
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset"
import { Field, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"

const styles = stylex.create({ fieldset: { width: "20rem" } })

export default function FieldsetDemo() {
  return (
    <Fieldset xstyle={styles.fieldset}>
      <FieldsetLegend>Personal information</FieldsetLegend>
      <Field>
        <FieldLabel>First name</FieldLabel>
        <Input placeholder="Jane" />
      </Field>
      <Field>
        <FieldLabel>Last name</FieldLabel>
        <Input placeholder="Doe" />
      </Field>
    </Fieldset>
  )
}

Installation

Install the following dependencies:

pnpm add @base-ui/react @stylexjs/stylex

Set up the token layer and the StyleX compiler from Installation, including state-styles.ts. Install Stack for fieldset layout.

Copy and paste the following code into your project.

components/ui/fieldset.tsx
"use client"

import type { CSSProperties } from "react"
import { Fieldset as FieldsetPrimitive } from "@base-ui/react/fieldset"
import * as stylex from "@stylexjs/stylex"
import type { StyleXStyles } from "@stylexjs/stylex"
import { VStack, type SpacingStep } from "@/components/ui/stack"
import { stateStyles } from "@/lib/state-styles"
import { leading, space, text } from "@/theme/tokens.stylex"

const styles = stylex.create({
  root: { minWidth: 0 },
  legend: { marginBlockEnd: space.s1_5, fontWeight: 500 },
})
const variants = stylex.create({
  legend: { fontSize: text.base, lineHeight: leading.base },
  label: { fontSize: text.sm, lineHeight: leading.sm },
})
type FieldLegendVariant = keyof typeof variants
type Overrides = { xstyle?: StyleXStyles; className?: string; style?: CSSProperties }
function Fieldset({
  gap = 4,
  xstyle,
  className,
  style,
  render,
  ...props
}: Omit<FieldsetPrimitive.Root.Props, "className" | "style"> & Overrides & { gap?: SpacingStep }) {
  return (
    <FieldsetPrimitive.Root
      data-slot="fieldset"
      {...props}
      render={(rootProps, state) => (
        <VStack
          {...rootProps}
          xstyle={[styles.root, xstyle]}
          render={typeof render === "function" ? (p) => render(p, state) : (render ?? <fieldset />)}
          gap={gap}
        />
      )}
      className={className}
      style={style}
    />
  )
}
function FieldsetLegend({
  variant = "legend",
  xstyle,
  className,
  style,
  ...props
}: Omit<FieldsetPrimitive.Legend.Props, "className" | "style"> &
  Overrides & { variant?: FieldLegendVariant }) {
  return (
    <FieldsetPrimitive.Legend
      data-slot="fieldset-legend"
      data-variant={variant}
      {...props}
      {...stateStyles(() => [styles.legend, variants[variant], xstyle], { className, style })}
    />
  )
}
const fieldsetStyles = { ...styles, variants }
export { Fieldset, FieldsetLegend, fieldsetStyles, type FieldLegendVariant }

Update the import paths to match your project setup.

Usage

import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset"
import { Field, FieldLabel } from "@/components/ui/field"

import { Input } from "@/components/ui/input"

;<Fieldset>
  <FieldsetLegend>Personal information</FieldsetLegend>
  <Field>
    <FieldLabel>First name</FieldLabel>
    <Input />
  </Field>
</Fieldset>

Anatomy

Fieldset contains a FieldsetLegend followed by related Field controls.

API reference

Accepts Base UI Fieldset props, including render, plus xstyle, className and inline style.

Fieldset

PropTypeDefault
gapSpacingStep4

FieldsetLegend

PropTypeDefault
variant"legend" | "label""legend"

fieldsetStyles

KeyWhat it is
rootShrinkable fieldset
legendLegend weight and bottom space
variantsLegend and label type sizes

Data attributes

AttributeOnEffect
data-slot="fieldset"rootFieldset anatomy
data-slot="fieldset-legend"legendLegend anatomy
data-variantlegendType size
data-disabledroot and legendBase UI disabled state