Form Structure
Field, FieldRow, FormGroup, Fieldset, FieldControl, and ValidationMessage: the layout and validation layer for Onset forms.
Components in this category
Wraps an input with a label, description and validation message.
FieldRowSide-by-side field layout.
FormGroupGroups fields and manages collective validation state.
FieldsetSemantic field set with legend.
FieldControlWraps a control inside a field with consistent spacing.
ValidationMessageValidates an error or hint message for a field.
Field
Field wraps an input with label, description hint, and validation error in one vertical row. It publishes an error state to the controls below via an internal context: any Control-based input inside reads that state and applies its own invalid styling without a per-control flag.
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | — | The field label. |
hint | ReactNode | — | Dim helper line under the control. |
error | ReactNode | — | Validation error message. Replaces hint when set. |
required | boolean | — | Shows a '*' next to the label. |
labelMeta | ReactNode | — | Right-aligned meta next to the label (e.g. remaining chars). |
htmlFor | string | — | Associates the label with the input's id. |
children | ReactNode | — | The input control. |
The error context cascades: every Input, Textarea, and NumberInput inside an errored Field renders with a red border and focus ring. An explicit invalid prop on the control itself still wins.
import { Field, Input } from "@solarius/onset";
<Field label="API Key" hint="Found in Settings → API Keys" required>
<Input placeholder="sk_live_..." />
</Field>;FieldRow
FieldRow places multiple Fields on one line with even widths: the standard pattern for amount/currency, start/end date, or city/zip pairs.
import { FieldRow } from "@solarius/onset";
<FieldRow>
<Field label="Amount">
<Input prefix="$" />
</Field>
<Field label="Currency">
<Select options={currencyOptions} />
</Field>
</FieldRow>;FormGroup
FormGroup groups related form rows under one shared validation + disabled state. Anything inside can read that state via useFormGroup(), so a server-side error on "billing address" can mark all fields in the group at once instead of being threaded through each of them.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Form rows. |
disabled | boolean | — | Greys out every descendant. |
required | boolean | — | Marks every FieldControl inside as required. |
tone | ValidationTone | — | Validation tone inherited by descendants: error, warning, or success. |
message | ReactNode | — | Validation summary rendered under the group. |
layout | "stack" | "row" | "stack" | Stack or side-by-side layout. |
gap | "tight" | "normal" | "loose" | "normal" | Row spacing. |
import { FormGroup, useFormGroup, Field, Input } from "@solarius/onset";
<FormGroup
disabled={loading}
tone={serverError ? "error" : undefined}
message={serverError ? "Billing address is invalid" : undefined}
>
<Field label="Street">
<Input placeholder="123 Main St" />
</Field>
<Field label="City">
<Input placeholder="San Francisco" />
</Field>
<Field label="ZIP">
<Input placeholder="94105" />
</Field>
</FormGroup>;The useFormGroup() hook returns { tone, message, disabled, required, idPrefix } or null outside a group. Controls can opt into group state without requiring a specific wrapper.
Fieldset
Semantic <fieldset> with a styled legend, sharing FormGroup's validation context. Use this (not FormGroup) when the grouping is meaningful to assistive tech: radio sets, address blocks, permission matrices.
| Prop | Type | Default | Description |
|---|---|---|---|
legend | ReactNode | — | The fieldset's legend text. |
description | ReactNode | — | Dim explanatory line under the legend. |
action | ReactNode | — | Right-aligned control in the legend row (e.g. a Switch). |
children | ReactNode | — | Form content. |
bordered | boolean | true | Draws the bordered box. |
tone | ValidationTone | — | Validation tone. |
message | ReactNode | — | Validation message. |
import { Fieldset } from "@solarius/onset";
<Fieldset
legend="Notifications"
description="Manage email preferences"
action={<Switch />}
>
<Field label="Email">
<Input type="email" />
</Field>
</Fieldset>;FieldControl
FieldControl is a form row that inherits the surrounding FormGroup's disabled and validation state, and hands its control a generated id via render prop so label association does not have to be wired by hand.
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | — | The row label. |
children | ReactNode | ((props: { id, disabled, invalid }) => ReactNode) | — | The control, or a render prop receiving derived props. |
hint | ReactNode | — | Helper text under the control. |
tone | ValidationTone | — | Overrides the group's tone for this row only. |
message | ReactNode | — | Validation message for this row. |
required | boolean | — | Overrides the group's required flag. |
disabled | boolean | — | Overrides the group's disabled flag. |
labelMeta | ReactNode | — | Right-aligned meta next to the label. |
import { FieldControl } from "@solarius/onset";
<FieldControl label="Team name" required>
{({ id, invalid }) => (
<Input id={id} invalid={invalid} placeholder="My team" />
)}
</FieldControl>;ValidationMessage
Single-line validation note with a matching icon: error, warning, or success. Used internally by FormGroup and Fieldset, but exported standalone so a custom form layout can display its own summaries without adopting the full group model.
| Prop | Type | Default | Description |
|---|---|---|---|
tone | ValidationTone | "error" | Validation tone controlling icon and color: error, warning, or success. |
children | ReactNode | — | The message text. |
import { ValidationMessage } from "@solarius/onset";
import type { ValidationTone } from "@solarius/onset";Putting it together
A realistic form section: Fieldset with a toggle action, validation tone from a server error, and a FieldRow for the city/state pair: