Fieldset
Introduction
Groups related form controls under a native <fieldset>, with an accessible
legend, helper text, and error text. Unlike Field, it has no validator of
its own — it's a static, server-rendered grouping primitive, so it never
hydrates as a client island. It provides its disabled/invalid/required
state as context to nested Fields, matching Ark UI's Fieldset — see
Context propagation.
Usage
import { Fieldset, Field } from "../components/ui";
export default function MyPage() {
return (
<Fieldset
legend="User Profile"
helperText="Manage your info."
required
errorText="Something went wrong."
>
<Field>...</Field>
</Fieldset>
);
}
Passing errorText is enough to put the fieldset into its invalid state —
you don't need to also pass invalid. Pass invalid={false} explicitly if
you want to suppress the error styling while still keeping the text around.
Context propagation to nested Fields
Fieldset exposes its disabled/invalid/required state as context.
Every nested Field reads it as a fallback for its own — so a group-level
disabled or required doesn't need to be repeated on each field:
<Fieldset legend="Shipping address" disabled required>
<Field label="Street" /> {/* renders disabled + required */}
<Field label="Apt #" required={false} /> {/* opts back out of required */}
</Fieldset>
A Field's own prop always wins over the inherited one. For invalid
specifically, inheritance only applies when the Field has no validation of
its own (no validator, minLength, or explicit invalid prop) — a Field
that validates its own value is never silently overridden by the group.
This propagation is one level: only Field (and anything built on top of it,
like Textarea) consults Fieldset's context. A bare Switch or Checkbox
placed directly inside a Fieldset — with no wrapping Field — won't pick
up the group's styling, though native <fieldset disabled> still blocks
interaction with it regardless.
Composition
children is always the group of actual form controls — Fieldset wraps it
in a spacing container for you. legend / helperText / errorText accept
any Child, not just strings, so rich content (an icon, a badge) belongs
there rather than in children:
<Fieldset legend={<>Profile <Badge>New</Badge></>}>
<Field>...</Field>
<Textarea>...</Textarea>
</Fieldset>
Don't place FieldsetLegend (or FieldsetHelperText / FieldsetErrorText)
inside children — Fieldset wraps children in a <div>, and browsers
only use a <legend> to compute the fieldset's accessible name when it's a
direct child of <fieldset>, not when it's nested inside a wrapper. The
exported sub-components (FieldsetLegend, FieldsetHelperText,
FieldsetErrorText, FieldsetContent, FieldsetControl,
FieldsetRequiredIndicator) exist for reuse in fully hand-built markup, not
as an alternate way to compose within Fieldset itself.
CMS Page Builder
This component is available as a fieldset block in the Page Builder (content/pages/*.json):
{
"type": "fieldset",
"legend": "User Profile",
"helperText": "Manage your info.",
"required": true,
"children": [
{ "type": "field", "label": "Name" }
]
}
Props
| Prop | Type | Description |
|---|---|---|
children | any | Form controls to render inside the fieldset. |
class | string | Custom CSS classes. |
id | string | Unique identifier. Auto-generated if omitted. |
disabled | boolean | Disables the fieldset. Native <fieldset disabled> automatically disables every descendant control, and nested Fields pick it up as context too — see Context propagation. |
invalid | boolean | Whether the fieldset is in an invalid state. Defaults to true whenever errorText is set, so you usually don't need to pass this explicitly. |
required | boolean | Marks the group as required and appends a required indicator to the legend. |
legend | Child | The legend text for the fieldset. Always rendered as a direct child of <fieldset> — see Composition. |
helperText | Child | Helper text shown below the legend. |
errorText | Child | Error text shown when the fieldset is invalid. |
CMS bindings
legend, helperText, errorText, disabled, invalid, and required
are exposed as editable fields in public/admin/config.yml, plus a
children list for nesting other blocks — the same convenience props
documented above. id/class and the composition sub-components
(FieldsetContent, FieldsetControl, FieldsetRequiredIndicator, etc.) are
not exposed: they're a JSX-level API for developers hand-authoring pages, not
something a JSON-driven CMS block can express.