1
ComponentsForm Controls

Field

Field connects one form control with its label, description, validation state, and error message.

Purpose

Field connects one form control with its label, description, validation state, and error message.

Labeled control

Used for workspace notifications.

import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function Example() {
  return (
    <Field name="email">
      <FieldLabel>Email</FieldLabel>
      <Input type="email" />
      <FieldDescription>Used for notifications.</FieldDescription>
      <FieldError />
    </Field>
  );
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/field

Import

import { Field, FieldControl, FieldDescription, FieldError, FieldItem, FieldLabel, FieldValidity } from "@/components/ui/field";

Need the machine-readable source metadata? View registry JSON.

Usage

Use Field for every visible form control. It couples labels, descriptions and errors with the control automatically.

import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function Example() {
  return (
    <Field name="email">
      <FieldLabel>Email</FieldLabel>
      <Input type="email" />
      <FieldDescription>Used for notifications.</FieldDescription>
      <FieldError />
    </Field>
  );
}

Examples

States, control variants, validation and practical field patterns are compositions of the same primitive.

Disabled

Only workspace owners can change this address.

<Field disabled>
  <FieldLabel>Billing email</FieldLabel>
  <Input defaultValue="finance@ui.siteplane.io" disabled />
  <FieldDescription>
    Only workspace owners can change this address.
  </FieldDescription>
</Field>

Invalid

Please enter a valid email address.
<Field>
  <FieldLabel>Work email</FieldLabel>
  <Input aria-invalid defaultValue="not-an-email" />
  <FieldError match>Please enter a valid email address.</FieldError>
</Field>

With select

Controls where data is stored.

<Field>
  <FieldLabel>Region</FieldLabel>
  <Select defaultValue="eu-central" items={regions}>
    <SelectTrigger aria-label="Region" className="w-full">
      <SelectValue />
    </SelectTrigger>
    <SelectPopup>
      {regions.map(({ label, value }) => (
        <SelectItem key={value} value={value}>{label}</SelectItem>
      ))}
    </SelectPopup>
  </Select>
  <FieldDescription>Controls where data is stored.</FieldDescription>
</Field>

With switch

Sends a summary to your team every Monday.

<Field>
  <FieldLabel className="cursor-pointer">
    <Switch defaultChecked />
    Enable weekly digest
  </FieldLabel>
  <FieldDescription>
    Sends a summary to your team every Monday.
  </FieldDescription>
</Field>

With textarea

Markdown is supported.

<Field>
  <FieldLabel>Release notes</FieldLabel>
  <Textarea placeholder="What changed?" />
  <FieldDescription>Markdown is supported.</FieldDescription>
</Field>

Required

Slug: not submitted
<Form onSubmit={onSubmit}>
  <Field name="slug">
    <FieldLabel>Project slug</FieldLabel>
    <Input placeholder="my-project" required />
    <FieldError match="valueMissing">This field is required.</FieldError>
  </Field>
  <Button loading={loading} type="submit">Submit</Button>
</Form>

Live validation

FieldControl and FieldValidity expose the live validity state while typing.

Looks good.

<Field
  validate={(value) =>
    String(value).length < 3 ? "At least 3 characters." : null
  }
  validationMode="onChange"
>
  <FieldLabel id={labelId}>Display name</FieldLabel>
  <FieldControl aria-labelledby={labelId} defaultValue="mo" />
  <FieldValidity>
    {(state) => (
      <FieldDescription>
        {state.validity.valid === false ? "Still too short." : "Looks good."}
      </FieldDescription>
    )}
  </FieldValidity>
  <FieldError />
</Field>

Description and error

A descriptive name helps when revoking tokens later.

The name cannot be empty.
<Field>
  <FieldLabel>API token name</FieldLabel>
  <Input aria-invalid placeholder="e.g. CI deploy" />
  <FieldDescription>
    A descriptive name helps when revoking tokens later.
  </FieldDescription>
  <FieldError match>The name cannot be empty.</FieldError>
</Field>

Form integration

Visible to every member.

Workspace: not submitted
<Form onSubmit={onSubmit}>
  <Field name="workspace">
    <FieldLabel>Workspace name</FieldLabel>
    <Input defaultValue="plane" placeholder="Acme Inc." />
    <FieldDescription>Visible to every member.</FieldDescription>
  </Field>
  <Button loading={loading} type="submit">Submit</Button>
</Form>

Settings row

<Field>
  <label
    className="flex w-full cursor-pointer items-start justify-between gap-4"
    htmlFor={controlId}
  >
    <span className="grid min-w-0 gap-1">
      <span className="font-medium text-sm leading-5" id={labelId}>
        Two-factor auth
      </span>
      <FieldDescription id={descriptionId}>
        Require a second factor when signing in.
      </FieldDescription>
    </span>
    <Switch
      aria-describedby={descriptionId}
      aria-labelledby={labelId}
      defaultChecked
      id={controlId}
    />
  </label>
</Field>

Inline field

<Field>
  <div className="flex w-full items-center gap-3">
    <FieldLabel className="shrink-0" id={labelId}>Seats</FieldLabel>
    <Input
      aria-labelledby={labelId}
      className="max-w-28"
      defaultValue="5"
      inputMode="numeric"
    />
  </div>
</Field>

Field item

FieldItem groups a control with its own text inside a Field.

Choose which updates reach your inbox.

Release notes
Incident alerts
<Field>
  <FieldLabel>Notifications</FieldLabel>
  <FieldDescription>Choose which updates reach your inbox.</FieldDescription>
  <FieldItem className="items-center gap-2">
    <Switch defaultChecked />
    <span className="text-sm">Release notes</span>
  </FieldItem>
</Field>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Field invalid controls the complete external error state for form libraries and keeps label, control, and error presentation synchronized.
  • FieldLabel nativeLabel={false} renders non-native label semantics for button-like controls such as Select triggers, where a native label is not appropriate.
  • FieldItem disabled disables and dims one item inside a larger active group.
  • FieldControl supports controlled onValueChange(value, eventDetails) in addition to uncontrolled defaultValue.
  • Use FieldValidity when rendering validation details from Base UI state.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Field adds no local motion.
  • The contained control owns focus, pressed, popup, and validation transitions.
  • Error copy may appear without shifting the control itself.

Accessibility

  • Every visible control needs a connected FieldLabel or an accessible label.
  • Connect descriptions and errors through the Field context rather than manual duplicate IDs.
  • Keep invalid state on the Field and control as required by the selected primitive.
  • Use nativeLabel={false} only for non-labelable button-like controls; do not remove a control’s accessible name.

Implementation Guidance

  • Use Field as the standard wrapper for one control, label, description, and error.
  • Do not render raw inputs without Field or another correctly connected label.
  • Prefer Field props and Base UI context to manual validation wiring.

On This Page