Skip to content

Components · Forms

Field

Label, control, help and error wired together with the right ARIA.

@rookcom/monarch-reactComponent

Validation wiring

Used for sign-in codes only.

Enter a valid email address.

Usage examples

Native select

Compact toolbar select

Switch with description

Purpose

Groups a label, one form control, help text and an error message, and wires their ids and ARIA attributes together so every Monarch control is labelled and described correctly.

Use when

  • Any Monarch form control (Input, Textarea, Select, Checkbox, Switch, Combobox) needs a visible label, help text or validation message.
  • A custom control should pick up the same wiring: call useFieldControl(props) inside it.
  • The platform picker is the better control (long lists on phones, no-JavaScript forms, compact toolbar filters): use NativeSelect.
  • A Switch needs a label and description beside it: layout="row".

Avoid when

  • A Checkbox or Switch with its own inline label prop: it already labels itself.
  • A control labelled only by surrounding context (a search box with aria-label).

Design principles

  • Wiring is automatic: the control receives id, aria-describedby, aria-invalid, aria-required and disabled from the Field.
  • aria-describedby only names parts that are rendered: FieldDescription and FieldError register themselves.
  • Explicit props on the control always win, so controls also work outside a Field.
  • FieldError can always be present: it renders nothing when its children are empty.
  • NativeSelect is the real <select>: it keeps the platform picker, form submission and typeahead, and only restyles the closed control.
  • Layout is a prop, not a new component: layout="row" rearranges the same parts.

API reference

import { Field, FieldDescription, FieldError, FieldLabel, NativeSelect, useFieldContext, useFieldControl } from '@rookcom/monarch-react';
ExportKindRenderingRoot class
Fieldcomponentclient.mn-field-
FieldDescriptioncomponentclient.mn-field-
FieldErrorcomponentclient.mn-field-
FieldLabelcomponentclient.mn-field-
NativeSelectcomponentclient.mn-field-
useFieldContexthookclient–
useFieldControlhookclient–

Field renders a <div class="mn-field"> and provides context; FieldLabel forwards its ref to the <label>.

useFieldControl(props, { invalid }) merges Field context into a control’s props; useFieldContext() returns the raw context, or null outside a Field.

NativeSelect forwards its ref to the <select>; all native select attributes pass through. It renders <span class="mn-native-select"> around the select and its chevron.

Generated from the TypeScript declarations of packages/react/src/components/forms/form-field.

Properties

Field id overrides the generated control id; the label, description and error ids derive from it (<id>-label, <id>-description, <id>-error).

Field

Groups a label, a control, help text and an error message, and wires their ids together: the control gets `id`, `aria-describedby`, `aria-invalid`, `aria-required` and `disabled` automatically. Descriptions and errors register themselves, so `aria-describedby` only names elements that exist. <Field invalid={!!error}> <FieldLabel>Email</FieldLabel> <Input type="email" /> <FieldError>{error}</FieldError> </Field>

PropTypeDescription
children requiredReactNode
classNamestring
disabledboolean
idstringOverride the generated control id (e.g. to match an existing label).
invalidboolean
layout"stack" | "row"`stack` (default): label, control, help and error in a column. `row`: label and help on the left, the control on the right, for a Switch (or Checkbox, or a compact NativeSelect) with a description. Exposed as `data-layout="row"`.
requiredboolean

FieldDescription

PropTypeDescription
childrenReactNode
classNamestring
idstring

FieldError

Renders nothing when empty, so it can always be present. Announced politely when it appears.

PropTypeDescription
childrenReactNode
classNamestring
idstring

FieldLabel

PropTypeDescription
childrenReactNode
classNamestring
idstring
optionalTextstringText shown after the label for optional fields, e.g. "Optional".

NativeSelect

The platform `<select>`, styled like Input and wired to Field. Use it where the native picker is right: long option lists on mobile, forms that must work without JavaScript, compact toolbar filters. For a styled listbox use Select; for search use Combobox.

PropTypeDescription
childrenReactNode
classNamestring
disabledboolean
idstring
invalidbooleanMarks the value invalid. Inside a Field, the Field's `invalid` is used.
labelReactNodeVisible label rendered inline before the select, for toolbars and filter rows outside a Field ("Sort Newest ▾"). Inside a Field use FieldLabel.
optionsreadonly NativeSelectOption[]Options as data. `children` (`<option>`, `<optgroup>`) are rendered after them.
placeholderstringFirst, empty option shown while no value is chosen. It is disabled once the control is `required`, so it cannot be submitted.
size"sm" | "md" | "lg"Control height. `sm` is the compact toolbar size (`--mn-control-sm`). Default `md`.
wrapperClassNamestringClass for the wrapper `<span class="mn-native-select">`; `className` goes on the `<select>`.

Variants

PropValuesDescription
FieldLabel optionalText'string'Text after the label for optional fields, for example "Optional". Not shown when the Field is required, which shows a red asterisk instead.
Field layout'stack''row'stack (default) is a column. row puts the label and description on the left and the control on the right, with the error under both: the switch-with-description row. Exposed as data-layout="row".
NativeSelect size'sm''md''lg'Height from --mn-control-*, matching Input. sm (2rem) is the compact toolbar size.
NativeSelect label'ReactNode'Inline visible label before the control, for toolbars outside a Field (data-inline-label). Inside a Field, use FieldLabel instead.

States

StateExposed asBehaviour
Invaliddata-invalid on the Field, aria-invalid="true" on the controlThe error message is added to aria-describedby only while the Field is invalid and FieldError has content.
Requiredrequired and aria-required="true" on the controlFieldLabel shows an aria-hidden asterisk; the requirement is announced from the control.
Disableddata-disabled on the Field, disabled on the controlThe label switches to the disabled text colour.
NativeSelect placeholder<option value="" disabled> when requiredplaceholder adds an empty first option; on a required control it is disabled so it cannot be chosen again.

Interaction behaviour

  • Clicking the label focuses the control (htmlFor points at the control id).
  • When validation changes, FieldError registers and unregisters itself, so aria-describedby and aria-invalid update with it.
  • FieldError is an aria-live="polite" region, so a new message is announced.
  • NativeSelect opens the platform picker; clicking its inline label focuses it.

Keyboard support

KeysAction
TabMoves to the control; Field and its label add no tab stop.
Arrow keys, typing, Space / Alt+ArrowDownNativeSelect: the platform behaviour of <select> (change option, typeahead, open the picker).

Field adds no key handling of its own. Keyboard behaviour is the control’s; the label is reachable by clicking only.

Accessibility

Role
–
Accessible name
FieldLabel, linked to the control with htmlFor.
  • The control is described by FieldDescription and, while invalid, FieldError, in that order.
  • The required asterisk is aria-hidden; aria-required carries the meaning.
  • Select trigger receives aria-required but not required, which is invalid on a button.
  • Ids come from useId, so they are stable between server and client render.
  • NativeSelect is a real <select> (role combobox or listbox by browser), so screen readers announce it natively. Outside a Field, give it label or aria-label.
  • The chevron is decorative (aria-hidden).

Responsive behaviour

  • A vertical stack (label, control, help, error) that fills its container at every width.
  • layout="row" keeps the control on the right at every width; the text column shrinks and wraps first (minmax(0, 1fr)).
  • NativeSelect fills a stacked Field; on its own it is inline and shrinks with an ellipsis rather than overflowing.

Theme support

  • Label, help and error colours are semantic tokens (--mn-color-text-*, --mn-color-danger-strong), so they follow light, dark and more-contrast themes.
  • The error colour does not change with the product accent.
  • NativeSelect reuses the Input surface, border and focus tokens; the open picker is drawn by the platform and follows color-scheme.

Do and don't

Do

  • Keep FieldError mounted and pass null when there is no error, so the wiring updates in place.
  • Set invalid on the Field, not on the control, when the control is inside a Field.
  • Write errors that say how to fix the value ("Enter a valid email address.").

Don't

  • Do not put more than one control in a Field: the label and descriptions point at one id.
  • Do not rely on the asterisk alone to explain a required field when most fields are optional; use optionalText on the optional ones instead.
  • Do not style a raw <select> per page: use NativeSelect so it picks up Field wiring and tokens.
  • Input: Input and Textarea, the most common Field controls.
  • Select: Single-value picker that reads Field context.
  • Combobox: Searchable picker that reads Field context.
  • SettingsSection: SettingsRow builds on Field for settings pages.
  • Switch: The control in a layout="row" switch row.
  • SwitchSettingsList: A list of switch rows that each save on their own, with saving and error states.

Dependencies

KindDepends on
npm packagesreact
Monarch componentsNone

Testing coverage

  • Wires label, description and error to the control (required, aria-invalid, accessible description).
  • Only references descriptions that exist.
  • Adds and removes the error reference as validation changes.
  • Propagates disabled to the control.
  • Works in StrictMode (double effects).
  • Text-field tests prove explicit props win outside a Field; Select tests prove a trigger gets aria-required, not required.
  • NativeSelect: Field wiring (name, description, error, required), disabled placeholder and options, uncontrolled and controlled changes, inline label naming and focus, Tab focus, disabled from Field.
  • layout="row" labels and describes a Switch, the label toggles it, and the default stacked markup has no data-layout.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery; Cross-browser axe: /components/field

Tests: packages/react/src/components/forms/form-field