Files
skills/docs/ui/react/react-ui/src/components/field-set/field-set.md
2026-05-29 08:20:56 +01:00

6.4 KiB

FieldSet (suaveplan-only)

(no submodules)

- field-set.variants — `fieldSetVariants` and `fieldL…
  • field-set.variants — fieldSetVariants and fieldLegendVariants are the cva (class-variance-authority) configurations powering `

    …

  • field-set.variants — fieldSetVariants and `fieldL…

  • field-set.variants — fieldSetVariants and fieldLegendVariants are the cva (class-variance-authority) configurations powering `

    …

  • field-set.variants — fieldSetVariants and `fieldL…

  • field-set.variants — fieldSetVariants and fieldLegendVariants are the cva (class-variance-authority) configurations powering `

    …

  • field-set.variants — fieldSetVariants and `fieldL…

  • field-set.variants — fieldSetVariants and fieldLegendVariants are the cva (class-variance-authority) configurations powering `

    …

  • field-set.variants — fieldSetVariants and fieldLegendVariants are the cva (class-variance-authority) configurations powering `

    …

Purpose

<FieldSet> and <FieldLegend> are chassis primitives that wrap related controls (typically Checkbox / RadioGroup / Switch groups) inside the native <fieldset> + <legend> HTML5 elements. The native <fieldset disabled> attribute propagates to every contained form control automatically, which <Field> (a <div>) cannot replicate. Pair <FieldLegend> with <FieldSet> to provide an accessible grouping heading announced by screen readers when navigating into any contained control.

Suaveplan addition — shadcn does not ship a FieldSet registry item upstream. The chassis adds these primitives because the cross-cutting reconciliation note (#1) flagged the gap: <Field> is a <div> and cannot carry the native <fieldset> semantics. Documented as suaveplan-only so consumers know the source of truth lives in this package, not upstream shadcn.

Features

  • <FieldSet> renders the native <fieldset> so disabled propagates to every contained control via HTML5 semantics.
  • <FieldLegend> renders the native <legend> so screen readers announce the grouping when navigating into any contained control.
  • data-invalid="true" on <FieldSet> flips descendant <FieldLegend> colour to destructive via the cva data-[invalid=true]: selector.
  • data-disabled="true" mirrors the native disabled so sibling cva selectors can react.
  • Refs forwarded on both primitives.
  • Theme-driven variant overrides via useThemedVariants("FieldSet" | "FieldLegend").
  • Layout uses flex flex-col gap-3 (NOT space-y-* per shadcn skill rule).

Usage: Basic Example

import { FieldSet, FieldLegend, Checkbox } from "@suaveplan/react-ui";

export function PermissionsFieldSet() {
    return (
        <FieldSet>
            <FieldLegend>Permissions</FieldLegend>
            <div className="flex items-center gap-2">
                <Checkbox id="read" defaultChecked />
                <label htmlFor="read">Read</label>
            </div>
            <div className="flex items-center gap-2">
                <Checkbox id="write" />
                <label htmlFor="write">Write</label>
            </div>
        </FieldSet>
    );
}

Usage: Advanced Example

import {
    FieldSet,
    FieldLegend,
    RadioGroup,
    RadioGroupItem,
} from "@suaveplan/react-ui";
import { useState } from "react";

export function NotificationFrequency() {
    const [enabled, setEnabled] = useState(true);
    const [freq, setFreq] = useState("daily");

    // Disabling the entire fieldset propagates `disabled` to every
    // contained <RadioGroupItem> via HTML5 fieldset semantics.
    return (
        <FieldSet disabled={!enabled}>
            <FieldLegend>Notification frequency</FieldLegend>
            <RadioGroup value={freq} onValueChange={setFreq}>
                <div className="flex items-center gap-2">
                    <RadioGroupItem value="daily" id="freq-daily" />
                    <label htmlFor="freq-daily">Daily digest</label>
                </div>
                <div className="flex items-center gap-2">
                    <RadioGroupItem value="weekly" id="freq-weekly" />
                    <label htmlFor="freq-weekly">Weekly summary</label>
                </div>
            </RadioGroup>
        </FieldSet>
    );
}

API Reference

<FieldSet>

forwardRef<HTMLFieldSetElement, FieldSetProps> — renders the native <fieldset>.

FieldSetProps

Inherits every prop from FieldsetHTMLAttributes<HTMLFieldSetElement>. Adds:

Prop Type Default Description
invalid boolean false Drives data-invalid="true" on the container so descendant <FieldLegend> flips to destructive colour.

The native disabled attribute is forwarded as-is and additionally drives data-disabled="true" for cva selectors.

<FieldLegend>

forwardRef<HTMLLegendElement, FieldLegendProps> — renders the native <legend>.

FieldLegendProps

Inherits every prop from HTMLAttributes<HTMLLegendElement>. Adds:

Prop Type Default Description
invalid boolean false Drives data-invalid="true" on the legend itself so the destructive colour activates without depending on a parent <FieldSet invalid>.

Cva resolvers

fieldSetVariants and fieldLegendVariants are exported as bare cva resolvers for external composition.

Implementation Notes

  • This is a suaveplan-only addition — shadcn does not ship a field-set registry item upstream. The chassis adds it because <Field> (a <div>) cannot carry the native <fieldset> semantics, and the cross-cutting reconciliation note (#1) flagged the gap.
  • The native <fieldset disabled> attribute propagates disabled to every contained form control automatically. The chassis preserves this semantic by rendering the actual HTML5 element rather than wrapping in a <div role="group">.
  • The <FieldLegend> invalid prop is exposed independently of <FieldSet> so consumers can render a legend in error state outside a fieldset (e.g. a custom layout that uses the chassis legend styling without the fieldset wrapper).
  • Per the shadcn skill rule, the cva NEVER uses space-y-* — gap-3 flows the same in RTL.
  • Refs target the native <fieldset> / <legend> so consumers can measure or scroll-into-view programmatically.