Field
Wires every control's label, description and error: a Base UI Field.Root whose label, control, description and error stack in that order.
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from '@fairgarden-private/design/components/Field'
Labels are authored in title case up to about four words and sentence case beyond; helper text is sentence case. Put "(optional)" in the label with optional, never an asterisk alone. While Base UI reports the field invalid, the Field passes the danger scale to its error and, through context, to its text-entry or select control; value controls keep their own secondary. For a Select or Combobox, label the trigger with <FieldLabel nativeLabel={false}>.
Base UI validates on submit and then re-validates a failed field as it changes. To show a server error, pass invalid to the Field and match to its FieldError.
States
States
A rest field with a description, an optional field, an invalid field (the danger ◆ plus a message that says how to fix it) and a disabled field, whose label and value take the muted ink and whose edge turns dotted.
We send one trail report a month.
Assigned when your membership starts.
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from '@fairgarden-private/design/components/Field'
import { Input } from '@fairgarden-private/design/components/Input'
import styles from './states.module.css'
/**
* Rest, optional, invalid and disabled fields. The invalid field passes the
* danger scale to its control and error; the label stays --primary12.
*/
export function FieldStates() {
return (
<div className={styles.stack}>
<Field>
<FieldLabel>Email Address</FieldLabel>
<Input type="email" placeholder="Email address…" />
<FieldDescription>We send one trail report a month.</FieldDescription>
</Field>
<Field>
<FieldLabel optional>Phone Number</FieldLabel>
<Input type="tel" />
</Field>
<Field invalid>
<FieldLabel>Postcode</FieldLabel>
<Input defaultValue="12" style={{ inlineSize: '8ch' }} />
<FieldError match>Postcode is too short. Enter all 5 digits.</FieldError>
</Field>
<Field disabled>
<FieldLabel>Member Number</FieldLabel>
<Input defaultValue="FG-2041" />
<FieldDescription>Assigned when your membership starts.</FieldDescription>
</Field>
</div>
)
}
Primary and secondary
Primary and secondary
primary sets the label and, by inheritance, the control's edge, value and ring. secondary is unused at rest: while the field is invalid the danger scale replaces it.
Primary plum: label, edge, value and ring.
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from '@fairgarden-private/design/components/Field'
import { Input } from '@fairgarden-private/design/components/Input'
import styles from './color.module.css'
/**
* `primary` recolors the label, description and, by inheritance, the
* control. While invalid the error always takes the danger scale.
*/
export function FieldColor() {
return (
<div className={styles.stack}>
<Field primary="plum">
<FieldLabel>Trail Name</FieldLabel>
<Input placeholder="Ridge loop…" />
<FieldDescription>Primary plum: label, edge, value and ring.</FieldDescription>
</Field>
<Field primary="slate" secondary="indigo" invalid>
<FieldLabel>Group Size</FieldLabel>
<Input defaultValue="40" />
<FieldError match>Groups are 12 or fewer. Enter 1–12.</FieldError>
</Field>
</div>
)
}
On grounds
On paper and forest
Fields stay open on every ground: the rule, muted and status roles resolve per ground, so the same markup reads on paper and on forest.
paper
As it appears on your card.
forest
As it appears on your card.
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from '@fairgarden-private/design/components/Field'
import { PresetGround } from '@/components/PresetGround'
import { Input } from '@fairgarden-private/design/components/Input'
import styles from './grounds.module.css'
const presets = ['paper', 'forest'] as const
/** The same fields on paper and forest: the roles resolve per ground. */
export function FieldGrounds() {
return (
<div className={styles.row}>
{presets.map((preset) => (
<PresetGround key={preset} preset={preset} className={styles.face}>
<p className={styles.name}>{preset}</p>
<Field>
<FieldLabel>Your Name</FieldLabel>
<Input placeholder="First and last name…" />
<FieldDescription>As it appears on your card.</FieldDescription>
</Field>
<Field invalid>
<FieldLabel>Email Address</FieldLabel>
<Input defaultValue="heron@" />
<FieldError match>Enter an email address, like name@example.com.</FieldError>
</Field>
</PresetGround>
))}
</div>
)
}
API Reference
Field
A Base UI Field.Root: the stack of label, control, description and error.
Validation follows §10.2: validate on submit, then on blur for a field
that has already failed (validationMode on the Form).
| Prop | Type | Description |
|---|---|---|
primary | | Primary Radix scale: the label, description and, by inheritance, the control’s edge, value and focus ring. Never defaulted [D133]. |
secondary | | Secondary Radix scale. Unused at rest; while the field is invalid the danger scale replaces it on the error and the control (§10.1). |
FieldLabel
The field label: type-field-label in --primary12, authored in title
case up to about four words, sentence case for longer or question labels
[D160]. For a Select or Combobox trigger pass nativeLabel={false}: the
label then renders a div that focuses the control without opening it.
| Prop | Type | Description |
|---|---|---|
optional | | Appends the marker inside the label (§10.1): |
FieldDescription and FieldError
Helper text: type-caption in --role-muted, --size-px-1 below the
box; sentence case. Base UI links it to the control with
aria-describedby.
The error message, shown by Base UI while the field is invalid (or for
the match it names): the danger glyph in --role-status, then the
message in type-caption at --font-weight-6 in --primary12. Word it
as what happened plus how to fix it (§1.9.2). Without children it shows
the browser’s validation message.
Additional types
dangerScale
The §1.5.4 danger scale, passed as secondary while a field is invalid [D129].
type dangerScale = 'red'FieldDescriptionProps
Props for FieldDescription: Base UI Field.Description props.
type FieldDescriptionProps = FieldDescription.FieldDescriptionPropsFieldErrorProps
Props for FieldError: Base UI Field.Error props, without render.
type FieldErrorProps = Omit<FieldError.FieldErrorProps, 'render'>FieldLabelProps
Props for FieldLabel: Base UI Field.Label props plus the “(optional)” marker.
type FieldLabelProps = FieldLabelProps & { optional?: boolean | string }FieldProps
Props for Field: Base UI Field.Root props plus the color axes.
type FieldProps = FieldRootProps & {
primary?:
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
secondary?:
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
}Specification: DESIGN-SYSTEM.md §10.1 (the field box and state grammar) and §10.2 (field, fieldset and form).