Skip to content

Field

Connect a form control to its label, description and error messages.

import { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Wrap form controls in Field to connect their labels, descriptions and errors for assistive technology. Group controls that answer one question with FieldSet and FieldLegend. A search or toolbar input already named by aria-label does not need a Field.

import { Field, FieldDescription, FieldError, FieldLabel, Input } from '@youj/react'

export function WorkspaceName({ error }: { error?: string }) {
  return (
    <Field required invalid={!!error}>
      <FieldLabel>Workspace name</FieldLabel>
      <Input name="workspace" />
      <FieldError>{error}</FieldError>
      <FieldDescription>Shown in the sidebar and in invite emails.</FieldDescription>
    </Field>
  )
}

Field is Base UI's Field, so the wiring is correct by construction. FieldLabel names the control, FieldDescription and a visible FieldError describe it, and invalid and disabled reach it. required is Youj's addition: set it once on the Field and the label shows the mark while the control becomes required. Every Youj control works inside a Field: Input, Textarea, Input Group, Checkbox, Radio Group, Switch, Slider and Select.

Reference

StructureField, FieldLabel, FieldSet… (10 parts)

Structure

PartRendersUse it for
FielddivOne control with its label, description and error.
FieldLabellabelThe control's name. Wrapping a whole Field makes a choice card.
FieldDescriptionpHelp text, added to the control's description.
FieldErrordivErrors, shown in a panel that grows in and collapses out.
FieldContentdivThe label-and-description column beside a horizontal control.
FieldTitledivA label-styled heading that doesn't label anything, for choice cards.
FieldSetfieldsetA group of controls that answer one question.
FieldLegenddivThe group's name, linked to its fieldset. variant="label" sets it at label size.
FieldGroupdivStacks fields and sets. It's the container that responsive measures.
FieldSeparatordivA hairline between groups, with optional text in the middle.

Outside a Field, the label, description and error parts render as plain elements with the same look, so you can compose them freely.

Labela label outside a Field

Label

Outside a Field, use Label: a plain <label> with the same type, linked to its control by htmlFor. required shows the same decorative mark. It ships as its own registry item, label, which Field installs for you.

import { Checkbox, Label } from '@youj/react'

<div className="flex items-center gap-2">
  <Checkbox id="terms" />
  <Label htmlFor="terms">Accept the terms</Label>
</div>
Custom controluseFieldControl, useFieldRequired

Custom control

A control Base UI doesn't know, such as a popover trigger or a row of segments, joins the surrounding Field with useFieldControl. It returns id (what the FieldLabel's htmlFor points at), labelId (for aria-labelledby on roles a native label can't name), describedBy (the Field's description and error), and the Field's invalid, disabled and required. Outside a Field it returns a stable id and nothing else. Pass { labelable: false } for a group whose first input already claims the label. useFieldRequired reads only the Field's required.

import { useFieldControl } from '@youj/react'

function ColorSwatch({ value }: { value: string }) {
  const { id, describedBy, invalid, disabled } = useFieldControl()
  return (
    <button id={id} type="button" aria-describedby={describedBy} aria-invalid={invalid || undefined} disabled={disabled}>
      {value}
    </button>
  )
}
Propsrequired, orientation, required… (7)

Props

Field

Field props
PropType
requiredboolean

Marks the label and makes the control required.

default false

false
orientation'horizontal' | 'vertical' | 'responsive'

default 'vertical'

'vertical'
Other props pass through: extends FieldPrimitive.Root.Props, VariantProps<typeof fieldVariants>.

These Base UI Field props do the rest:

PropTypeDescription
namestringNames the control's value when the form is submitted.
invalidbooleanMarks the field invalid, for validation you run yourself.
disabledbooleanDisables the control and dims the label.
validate(value, formValues) => string | string[] | nullCustom validation. Return the message, or nothing when valid. It can be async.
validationMode'onSubmit' | 'onBlur' | 'onChange'When validate and native constraints run. Default 'onSubmit'.
validationDebounceTimenumberWaits this many milliseconds between onChange validations. Default 0.

FieldLabel

FieldLabel props
PropType
requiredboolean

Shows the required mark. Defaults to the surrounding Field's required.

–
Other props pass through: extends FieldPrimitive.Label.Props.

FieldError

FieldError props
PropType
errorsArray<{ message?: string } | undefined>

react-hook-form style errors. Duplicates collapse; several become a list.

–
Other props pass through: extends FieldPrimitive.Error.Props.

FieldLegend

FieldLegend props
PropType
variant'legend' | 'label'

default 'legend'

'legend'

FieldSeparator

FieldSeparator props
PropType
childrenReactNode–

Label

Label props
PropType
requiredboolean

Shows the required mark.

–
Other props pass through: extends ComponentProps<'label'>.

FieldDescription, FieldContent, FieldTitle, FieldSet and FieldGroup add no props of their own.

MotionError in, error out, height, error out, text… · reduced motion

Motion

Motion tokens
MomentTokens
Error inthe panel grows from 0 height while the text fades in
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)
Error out, heightthe panel closes over the last message
duration.baseease.standard

220ms · cubic-bezier(0.2, 0, 0, 1)

220mscubic-bezier(0.2, 0, 0, 1)
Error out, textfades before the height finishes
duration.fastease.exit

140ms · cubic-bezier(0.4, 0, 1, 1)

140mscubic-bezier(0.4, 0, 1, 1)
Choice cardhover, background and border tint
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

Errors appear and disappear at once. Nothing else in a Field moves.

The panel clips only while it moves, so focus rings inside it are never cut off at rest.

Reduced motion

Errors appear and disappear instantly, so the layout below moves in one step instead of easing. The message, its alert and its link to the control are unchanged.

AccessibilityARIA, focus, announcements

Accessibility

  • FieldLabel names the control: with htmlFor for inputs, and with aria-labelledby for composite controls such as Checkbox and Switch.
  • FieldDescription and a shown FieldError are added to the control's aria-describedby, and invalid sets aria-invalid on it.
  • FieldError is role="alert", so an error announces itself when it appears. On submit, move focus to the first invalid control, as the first preview does.
  • The required asterisk is aria-hidden. The control's own required is what assistive technology announces.
  • FieldSet and FieldLegend make a named group, so a screen reader reads "Scopes, group" before the first checkbox.