Field
Layout and accessibility envelope that wires a label, description, error, and a form control together without managing form state.
<Field> is a layout and accessibility envelope. It generates stable IDs, wires
aria-labelledby, aria-describedby, aria-invalid, and aria-required onto
the wrapped control, and provides visual slots for a label, hint text, and an error
message. It does not own form state — you bring your own state management and pass
error down from wherever validation lives.
Field has two API modes: shorthand (the common case) and compound (an
escape hatch for custom layout). The shorthand API takes label, description,
and error as props directly on <Field>, so the child is just the control.
The compound API uses explicit sub-components (<Field.Label>, <Field.Description>,
<Field.Control>, <Field.Error>) for cases that need a non-standard slot layout.
Field is form-framework agnostic. It works equally well with React Hook Form,
Zod + manual state, or no library at all. The RHF pattern using <Controller>
is shown in the react-hook-form section.
Basic usage
Pass label and place the control as the child.
Field injects id, aria-labelledby, and related ARIA attributes onto the
child control automatically. The child does not need to set them manually.
With description and error
Pass description and error as props:
When error is set, Field renders the error message below the control and
sets aria-invalid on the child. When both description and error are
present, aria-describedby contains both IDs — assistive technology reads both.
Required
Pass required:
aria-required is forwarded to the control. Field renders a visual indicator
(a red asterisk by default) next to the label text, with a screen-reader-only
accessible label. Both strings are configurable via the NoriProvider dictionary:
| Key | Default | Purpose |
|---|---|---|
field.requiredIndicator | * | Visual character appended to the label |
field.requiredLabel | required | Accessible label on the indicator span |
Disabled
Pass disabled. The flag propagates to the child control and dims the label:
The child component's own disabled prop is OR-ed with the field-level value,
so you can also disable the control directly without going through the field.
Horizontal orientation
orientation="horizontal" renders the label and control side-by-side:
The default is "vertical".
Validating state
While an async validation is in flight, pass validating to show a spinner and
set aria-busy:
validating and error can coexist — the spinner renders alongside the error
message until the new result arrives.
Field.Group
Field.Group is a fieldset-style envelope for grouped controls such as radio
sets, checkbox groups, or multi-switch rows. It renders a role="group" container
labelled by its own label — equivalent to <fieldset> + <legend> semantics,
but using <View> so layout is not constrained by browser fieldset quirks.
Field.Group accepts the same shorthand props as Field (label, description,
error, required, disabled, orientation) except name. Pass name
directly to the inner input group.
react-hook-form (recommended)
Use <Controller> to connect Field to React Hook Form. Pass field.value,
field.onChange, and fieldState.error?.message through:
To inject a server-side error after submission, call setError:
react-hook-form is not a peer dependency of @nori-ui/core. Install it
separately:
Manual / framework-agnostic
Field works without any form library. Use useState for local state and pass
a derived error string:
Custom layout (compound API)
When you need full control over slot placement — a control + button row, animated error transitions, or a field with multiple controls — use the compound sub-components directly:
When to reach for the compound API:
- The control sits inside a row alongside other elements (e.g., a button).
- You need to animate the error message in/out independently.
- You have multiple controls sharing one label (e.g., first + last name in a
single row) and need to manage the
idwiring yourself.
Field.Control clones its single child and injects id, aria-labelledby,
and related ARIA attributes. Pass the error prop on <Field> even in compound
mode — Field.Control reads it to set aria-invalid, and Field.Error reads
it to render the message.
Standalone Label
For cases where <Field> is too much — standalone toggle rows or settings
entries — use <Label> directly. It renders the same
visual style and required indicator as Field.Label, wired by an explicit
htmlFor instead of auto-generated IDs.
API reference
<Field>
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | — |
description | ReactNode | — | — |
disabled | boolean | false | — |
error | ReactNode | null | — |
id | string | — | — |
label | ReactNode | — | — |
name | string | — | — |
orientation | enum | vertical | — |
required | boolean | false | — |
testID | string | — | — |
validating | boolean | false | — |
<Field.Label>
Renders a label text node wired to the field's generated ID. Clicking it moves focus to the control. Used only in the compound API.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Label text. |
<Field.Description>
Renders hint copy below the label. Its ID is wired to the control's
aria-describedby. Used only in the compound API.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Hint text. |
<Field.Error>
Renders the error message in the danger tone. Returns null when there is no
error and no children. Has role="alert" on web. Used only in the
compound API — in shorthand mode the error slot
is rendered automatically when error is set.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Override the message from the error prop on <Field>. |
<Field.Control>
Clones its single child element and injects accessibility props. Used only in the compound API.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactElement | — | A single form control element. |
Injected props (your control receives these automatically):
| Prop | Set when |
|---|---|
id | Always (uses Field's generated ID) |
aria-labelledby | Always |
aria-describedby | Field.Description or Field.Error is present |
aria-invalid | error is truthy |
aria-required | required is true |
disabled | disabled is true on Field |
name | name prop on Field is set and child has no own name |
<Field.Group>
Fieldset-style group container for sets of related controls (radio buttons,
checkboxes, multi-switch rows). Accepts the same shorthand props as <Field>:
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible group heading (rendered as <legend> equivalent). |
description | string | null | Hint text below the label. |
error | string | null | null | Validation error message. Sets aria-invalid on inner controls. |
required | boolean | false | Marks the group required. Appends visual indicator and sets aria-required. |
disabled | boolean | false | Disables all inner controls. |
orientation | "vertical" | "horizontal" | "vertical" | Stack direction for label and content. |
children | ReactNode | — | The grouped controls (e.g., Radio.Group, checkbox list). |