Forms
To build an accessible form, wrap each control in <Field> and let the primitives wire the IDs.
Label, Input, FieldHelp, and FieldError read the context, set the right htmlFor, and
link aria-describedby automatically.
A single field
<Field> is the unit. It generates a stable id, derives <id>-help and <id>-error, and exposes
them through context.
Three things happen without you wiring them:
<Label>picks uphtmlFor={fieldId}from context.<Input>receivesid={fieldId}andaria-describedby="<id>-help <id>-error".- The vertical rhythm — label, control, help — comes from
<Field>'s defaultgap="$1.5".
A form
A sign-in form is two fields, a submit button, and a stack to hold them.
The form takes any layout props you would write on a <Box>. gap="$space.4" controls the
spacing between fields; the spacing inside each field still comes from <Field>.
Validation states
<Field invalid required> flips the connected primitives. Input picks up aria-invalid="true"
and renders the error border; Label adds a red asterisk for required; FieldError becomes
visible with the right id and role="alert".
When invalid flips back to false, the error styling and the alert role come off automatically.
The component does not need to remember the previous state.
Group with Fieldset
A <Fieldset> groups related fields with a bordered surface and an optional legend. Inside the
fieldset, <Field> blocks behave the same as outside — context boundaries do not chain.
Use one <Fieldset> per logical group. Nested fieldsets are valid HTML but rarely worth the noise.