Field
Field wraps a label, a control, an optional description and an optional
error. It renders Headless UI's Field, which generates the control id and
wires for, aria-labelledby and aria-describedby between the parts, so
no ids are needed. Its disabled and invalid reach every ZUI control
inside through context.
import Field from '@zuilib/primitives/field'
import Label from '@zuilib/primitives/label'
import FieldDescription from '@zuilib/primitives/field-description'
import FieldError from '@zuilib/primitives/field-error'
Basic
Put the parts in any order; the wiring is by role. The
control receives the field's id, the Label its for, and the
FieldDescription and FieldError ids join the control's
aria-describedby.
Invalid
invalid sets data-invalid on the field and its Label /
FieldDescription / FieldError, and becomes the default invalid of
every control inside, so the control gets aria-invalid and the danger
border from one prop. FieldError accepts a string or any
{ message?: string } object, so a form library's field error can be
passed straight through.
Disabled
disabled disables every control inside (they read it from the field) and
sets data-disabled on the field and its parts. Leave it unset to inherit
from an enclosing Fieldset disabled.
With a toggle
Any ZUI control joins the field: a Checkbox, Switch, NativeSelect,
Select or Combobox is labelled and described the same way.
Props
FieldProps extends HTMLAttributes<HTMLDivElement> with:
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | Label, control, FieldDescription, FieldError in any order |
disabled | boolean | inherited | Disables every control inside and sets data-disabled. Unset inherits Fieldset disabled; an explicit false overrides it |
invalid | boolean | false | data-invalid on the field and its parts; the default invalid of every control inside |
className | string | — | Merged last after space-y-2 |
The module also exports useField() and FieldContext, which give a
custom control { disabled, invalid } of the enclosing field (null
outside one), and the FieldContextValue type.
import {useField} from '@zuilib/primitives/field'
function ColourPicker(props) {
const field = useField()
const invalid = props.invalid ?? field?.invalid ?? false
// ...
}
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="field"] | div | Root. data-invalid when invalid, data-disabled when disabled |
[data-slot="label"] | label | From Label; mirrors the field's data-invalid / data-disabled. Parts: label-required, label-unsaved |
[data-slot="field-description"] | p | From FieldDescription; same mirrored state |
[data-slot="field-error"] | p | From FieldError; same mirrored state |
/* Consumer CSS: tighter field spacing */
[data-slot="field"] > * + * {
margin-top: calc(var(--spacing) * 1);
}
Accessibility
- The control gets a generated
id, theLabela matchingfor(and the controlaria-labelledby), and eachFieldDescription/FieldErrorid is appended to the control'saria-describedby. invalidpropagates to the control asaria-invalid="true";FieldErrorrenders withrole="alert"(passrole="status"for a polite announcement).disabledis applied to every control inside, including from aFieldset disabledfurther up.- A control that sets its own
idinside aFieldneeds a matchinghtmlForon theLabel(set both or neither). - Outside a
Field,Label,FieldDescriptionandFieldErrorare plain elements: associate them withhtmlFor/id/aria-describedbyyourself.
Related
- Label, FieldDescription, FieldError: the parts.
- Fieldset: groups fields under a label and a shared
disabled. - Input, Textarea, NativeSelect, Checkbox: controls that read the field.