Fieldset
Primitive

Fieldset

Native field group primitives for a shared legend, description, error, and disabled/required/invalid state.

Live behavior

Fieldset in motion

Interactive
Atom behavior · App-owned appearance
Preparing behavior…

Interact with the specimen and inspect the behavior Atom contributes.

waiting for input

When to Use

Use Fieldset when several controls answer one question, such as a set of radio buttons or related checkboxes. Use Field when a label and messages belong to one control. A Fieldset should have a Legend so users understand what the whole group represents.

Features

The behavior Atom owns before your product adds appearance.

  • Renders native fieldset and legend elements.
  • Applies native fieldset disabled behavior to descendant form controls.
  • Registers Description and visible Error for aria-describedby.
  • Exposes required and invalid group state without invalid aria-required usage.
  • Supports required and optional legend indicators.
  • Exposes public optional and required context hooks.
  • Preserves server relationships when Root composes one fieldset wrapper with asChild.

Import

tsx
import { Fieldset } from "@flowstack-ui/atom";

Anatomy

tsx
<Fieldset.Root>
  <Fieldset.Legend />
  <Fieldset.Description />
  <Fieldset.Error />
</Fieldset.Root>

useFieldsetContext()
useRequiredFieldsetContext()
markFieldsetPart()

API Reference

Root

Renders a native fieldset, owns shared state, and emits server-stable relationships to its Description and currently visible Error.

PropTypeDefault
disabledbooleanfalse
requiredbooleanfalse
invalidbooleanfalse
validationBehavior"inline" | "native"Form value, then automatic
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-describedbyDescription and visible Error IDs unless explicitly provided
aria-invalid"true" when invalid

Root does not emit aria-required; required group state must be communicated through Legend text and the descendant controls where appropriate.

Data attributeValues
[data-slot]"fieldset"
[data-disabled]Present when disabled
[data-required]Present when required
[data-invalid]Present when invalid

Legend

Renders the native legend that names the fieldset and receives the stable ID used by nested CheckboxGroup or RadioGroup widgets.

PropTypeDefault
requiredIndicatorReactNode" *"
optionalIndicatorReactNode-
asChildbooleanfalse
renderRenderProp-

Plain required indicators are hidden from assistive technology; plain optional indicators remain readable.

Data attributeValues
[data-slot]"fieldset-legend"
[data-disabled]Present when Root is disabled
[data-required]Present when Root is required
[data-slot="fieldset-required-indicator"]Plain required indicator wrapper
[data-slot="fieldset-optional-indicator"]Plain optional indicator wrapper

Description

Renders a p with a server-stable generated ID for Root's description.

PropTypeDefault
asChildbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"fieldset-description"

Error

Renders only when Root is invalid or forceMatch is true and participates in Root's accessible description. It has no live role by default; pass native role="alert" or aria-live for a newly inserted announcement.

PropTypeDefault
forceMatchbooleanfalse
asChildbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"fieldset-error"

useFieldsetContext

Returns group state and generated relationships, or null outside Root.

useRequiredFieldsetContext

Returns the same context but throws outside Root. Use it for custom parts that require a Fieldset parent.

markFieldsetPart

Marks a styled public wrapper around Fieldset.Legend, Fieldset.Description, or Fieldset.Error so Root can include that wrapper in deterministic server-rendered naming and description relationships. Call it once at module scope after creating the wrapper. The wrapper must render the matching Atom part and forward its props and ref.

tsx
const StyledLegend = markFieldsetPart(
  forwardRef<HTMLLegendElement, FieldsetLegendProps>((props, ref) => (
    <Fieldset.Legend {...props} className="legend" ref={ref} />
  )),
  "legend",
);

Calling it again with the same kind is safe. A conflicting semantic kind throws rather than producing a silently incorrect server relationship.

Examples

Shipping Method

tsx
import { useState } from "react";
import { Fieldset, RadioGroup } from "@flowstack-ui/atom";

export function ShippingMethod() {
  const [method, setMethod] = useState("");
  const invalid = method === "";

  return (
    <Fieldset.Root id="shipping" required invalid={invalid}>
      <Fieldset.Legend>Shipping method</Fieldset.Legend>
      <Fieldset.Description>Choose how quickly the order should arrive.</Fieldset.Description>
      <RadioGroup.Root value={method} onValueChange={setMethod}>
        <RadioGroup.Radio value="standard">Standard</RadioGroup.Radio>
        <RadioGroup.Radio value="express">Express</RadioGroup.Radio>
      </RadioGroup.Root>
      <Fieldset.Error>Choose a shipping method.</Fieldset.Error>
    </Fieldset.Root>
  );
}

Accessibility

A grouped control reports native constraint failures to Root as one logical invalid state. A compatible Error part makes omitted behavior resolve to inline; otherwise it resolves to native. Inline behavior suppresses the browser bubble, reveals the group Error, and focuses the first enabled item without adding the group container to the Tab order.

Fieldset follows native WAI grouped-control guidance. Legend supplies the group name, native disabled disables descendant controls, and Root references its supporting messages. Required state is not valid on a fieldset through aria-required, so communicate it in the Legend and apply required semantics to the actual controls. Fieldset owns no keyboard behavior.

When Root uses asChild, the composed element must remain a native fieldset and forward Atom props and the ref. Keep Legend, Description, and Error direct children of that element so their generated server relationships remain deterministic.

Changelog

0.6.13

  • Added validation behavior inheritance and group-level native-invalid/Error presentation with first-enabled-control focus support.

0.5.2

  • Added markFieldsetPart for styled Legend, Description, and Error wrappers that must remain statically discoverable during server rendering.

0.5.1

  • Made Root asChild inspect the composed fieldset's immediate children so Legend, Description, and visible Error relationships remain present in server markup.

0.5.0

  • Added a stable Legend ID plus server-stable Description/Error relationships.
  • Added native naming and state integration for CheckboxGroup and RadioGroup, and removed the forced alert role from Error.

0.2.0

  • Removed invalid aria-required output from Fieldset.Root; required state remains available through Fieldset context and [data-required].

0.1.0

  • Initial Atom release.