# Field

Labels, descriptions and errors for form controls.

Source: [field.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/field.tsx)

```tsx
import {
  Field,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldLegend,
  FieldSet,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";

export default function FieldFieldset() {
  return (
    <FieldSet className="w-full max-w-sm">
      <FieldLegend>Profile</FieldLegend>
      <FieldDescription>Shown to other members of your team.</FieldDescription>
      <FieldGroup>
        <Field>
          <FieldLabel>Full name</FieldLabel>
          <Input defaultValue="Ada Park" />
          <FieldDescription>Use the name people know you by.</FieldDescription>
        </Field>
        <Field>
          <FieldLabel>Bio</FieldLabel>
          <Textarea placeholder="A sentence or two about yourself" />
        </Field>
      </FieldGroup>
    </FieldSet>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/field
```

## Usage

```tsx
import { Checkbox } from "@/components/ui/checkbox";
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldLegend,
  FieldSeparator,
  FieldSet,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function AccountForm() {
  return (
    <FieldSet>
      <FieldLegend>Account</FieldLegend>
      <FieldDescription>How we reach you.</FieldDescription>
      <FieldGroup>
        <Field>
          <FieldLabel>Email</FieldLabel>
          <Input type="email" />
          <FieldDescription>We send receipts here.</FieldDescription>
        </Field>
        <FieldSeparator />
        <Field orientation="horizontal">
          <Checkbox />
          <FieldContent>
            <FieldLabel>Marketing emails</FieldLabel>
            <FieldDescription>A few a year, at most.</FieldDescription>
          </FieldContent>
        </Field>
      </FieldGroup>
    </FieldSet>
  );
}
```

`FieldSet` groups related fields under a `FieldLegend`, and `FieldGroup` stacks them. Each `Field`
holds one control with its label, description and error. `FieldContent` keeps a label and
description together beside a control.

A `Field` links its parts to the control inside it, so the label names it and screen readers read
the description and error with it, without any `id`s. Every control built on Base UI takes part, as
do `Textarea` and `NativeSelect`. For the rest, such as `DatePicker`, point the `FieldLabel` at the
control's `id` with `htmlFor`.

Outside a `Field`, `FieldLabel` is a plain label. Wrap a checkbox, radio or switch in it to label
that control, or point it at one with `htmlFor`. An icon before its text is sized and dimmed to
match.

In a [Form](https://ui.jarv.is/components/form.md), give each `Field` a `name`. The form submits the control's value under
that name and validates it, and `FieldError` shows what went wrong.

## Composition

- `FieldSet`
  - `FieldLegend`
  - `FieldGroup`
    - `Field`
      - `FieldLabel`
      - `FieldDescription`
      - `FieldContent`
        - `FieldTitle`
      - `FieldError`
    - `FieldSeparator`
  - `FieldItem`

## Examples

### Invalid

Set `invalid` on the field, which marks the control invalid too, and put the message in
`FieldError`. It also takes an `errors` array of `{ message }` objects, as form libraries return.
Without either, `FieldError` shows the message from the field's own validation whenever it fails.

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

export default function FieldInvalid() {
  return (
    <Field invalid className="max-w-sm">
      <FieldLabel>Username</FieldLabel>
      <Input defaultValue="ada park" />
      <FieldError>Use letters, numbers and dashes.</FieldError>
    </Field>
  );
}
```

### Disabled

Set `disabled` on the field to disable its control and fade the label and description with it.

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

export default function FieldDisabled() {
  return (
    <Field disabled className="max-w-sm">
      <FieldLabel>Email</FieldLabel>
      <Input defaultValue="ada@example.com" />
      <FieldDescription>Managed by your organization.</FieldDescription>
    </Field>
  );
}
```

### Horizontal

`orientation="horizontal"` puts the control and label in a row, in the order you write them.

```tsx
import { Checkbox } from "@/components/ui/checkbox";
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldSeparator,
} from "@/components/ui/field";
import { Switch } from "@/components/ui/switch";

export default function FieldHorizontal() {
  return (
    <FieldGroup className="max-w-sm">
      <Field orientation="horizontal">
        <FieldContent>
          <FieldLabel>Weekly digest</FieldLabel>
          <FieldDescription>A summary of activity every Monday.</FieldDescription>
        </FieldContent>
        <Switch defaultChecked />
      </Field>
      <FieldSeparator />
      <Field orientation="horizontal">
        <Checkbox defaultChecked />
        <FieldContent>
          <FieldLabel>Product updates</FieldLabel>
          <FieldDescription>News about features and improvements.</FieldDescription>
        </FieldContent>
      </Field>
      <Field orientation="horizontal">
        <Checkbox />
        <FieldLabel>Tips and tutorials</FieldLabel>
      </Field>
      <Field orientation="horizontal">
        <Switch size="sm" />
        <FieldLabel>Play a sound for new messages</FieldLabel>
      </Field>
    </FieldGroup>
  );
}
```

### Radio group

Wrap each option of a radio or checkbox group in a `FieldItem`, which links its label and
description to it. `variant="label"` makes a `FieldLegend` look like a label instead of a heading.

```tsx
import { FieldItem, FieldLabel, FieldLegend, FieldSet } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";

export default function FieldRadioGroup() {
  return (
    <FieldSet className="w-full max-w-sm">
      <FieldLegend variant="label">Send notifications to</FieldLegend>
      <RadioGroup defaultValue="email">
        <FieldItem>
          <RadioGroupItem value="email" />
          <FieldLabel>Email</FieldLabel>
        </FieldItem>
        <FieldItem>
          <RadioGroupItem value="push" />
          <FieldLabel>Push notifications</FieldLabel>
        </FieldItem>
        <FieldItem>
          <RadioGroupItem value="none" />
          <FieldLabel>Nothing</FieldLabel>
        </FieldItem>
      </RadioGroup>
    </FieldSet>
  );
}
```

### With a separator

`FieldSeparator` is a [Separator](https://ui.jarv.is/components/separator.md) spaced to sit between fields. Children put a label
in the middle of it.

```tsx
import { Button } from "@/components/ui/button";
import { Field, FieldGroup, FieldLabel, FieldSeparator } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export default function FieldWithSeparator() {
  return (
    <FieldGroup className="max-w-sm">
      <Field>
        <FieldLabel>Email</FieldLabel>
        <Input type="email" placeholder="ada@example.com" />
      </Field>
      <Button variant="primary">Continue</Button>
      <FieldSeparator>or</FieldSeparator>
      <Button variant="secondary">Continue with SSO</Button>
    </FieldGroup>
  );
}
```

### Choice cards

Put a control and its `FieldContent` inside a `FieldLabel` to make the whole card clickable. Use
`FieldTitle` for its title, since a label can't contain another label.

```tsx
import { Checkbox } from "@/components/ui/checkbox";
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldTitle,
} from "@/components/ui/field";

export default function FieldChoiceCards() {
  return (
    <FieldGroup className="max-w-sm gap-3">
      <Field orientation="horizontal">
        <FieldLabel>
          <FieldContent>
            <FieldTitle>Email reports</FieldTitle>
            <FieldDescription>A usage summary at the end of each month.</FieldDescription>
          </FieldContent>
          <Checkbox defaultChecked />
        </FieldLabel>
      </Field>
      <Field orientation="horizontal">
        <FieldLabel>
          <FieldContent>
            <FieldTitle>Slack alerts</FieldTitle>
            <FieldDescription>A message when spending passes your limit.</FieldDescription>
          </FieldContent>
          <Checkbox />
        </FieldLabel>
      </Field>
    </FieldGroup>
  );
}
```

### Responsive

`orientation="responsive"` stacks the label and control until the `FieldGroup` is wide enough to
put them side by side.

```tsx
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

const languages = { en: "English", es: "Español", fr: "Français", de: "Deutsch" };

export default function FieldResponsive() {
  return (
    <FieldGroup className="max-w-xl">
      <Field orientation="responsive">
        <FieldContent>
          <FieldLabel>Language</FieldLabel>
          <FieldDescription>Used for emails and the interface.</FieldDescription>
        </FieldContent>
        <Select items={languages} defaultValue="en">
          <SelectTrigger>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {Object.entries(languages).map(([value, label]) => (
              <SelectItem key={value} value={value}>
                {label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </Field>
    </FieldGroup>
  );
}
```

## Props

`Field` has the `group` role, and `FieldError` the `alert` role. `FieldError`'s `match` picks the
failed check it shows for, as the [Form](https://ui.jarv.is/components/form.md) page explains.

| Part | Extends |
| --- | --- |
| `FieldSet` | [`Fieldset.Root`](https://base-ui.com/react/components/fieldset#root) |
| `FieldLegend` | [`Fieldset.Legend`](https://base-ui.com/react/components/fieldset#legend) |
| `FieldGroup` | `<div>` |
| `Field` | [`Field.Root`](https://base-ui.com/react/components/field#root) |
| `FieldItem` | [`Field.Item`](https://base-ui.com/react/components/field#item) |
| `FieldContent` | `<div>` |
| `FieldLabel` | [`Field.Label`](https://base-ui.com/react/components/field#label) |
| `FieldTitle` | `<div>` |
| `FieldDescription` | [`Field.Description`](https://base-ui.com/react/components/field#description) |
| `FieldSeparator` | [`Separator`](https://ui.jarv.is/components/separator.md) |
| `FieldError` | [`Field.Error`](https://base-ui.com/react/components/field#error) |

The tables leave out `className`, `style` and `render`. Parts built on Base UI take them as its [styling](https://base-ui.com/react/handbook/styling) and [composition](https://base-ui.com/react/handbook/composition) guides describe.

### FieldLegend

| Prop       | Type                  | Description         |
| ---------- | --------------------- | ------------------- |
| `variant?` | `"label" \| "legend"` | Default: `"legend"` |

### Field

| Prop                      | Type                                                                                                                               | Description                                                                                                                                                                                                                                                                                                                                                                             |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `disabled?`               | `boolean`                                                                                                                          | Whether the component should ignore user interaction. Takes precedence over the `disabled` prop on the `<Field.Control>` component. Default: `false`                                                                                                                                                                                                                                    |
| `name?`                   | `string`                                                                                                                           | Identifies the field when a form is submitted. Takes precedence over the `name` prop on the `<Field.Control>` component.                                                                                                                                                                                                                                                                |
| `validate?`               | `((value: unknown, formValues: Form.Values) => string \| string[] \| null \| void \| Promise<string \| string[] \| null \| void>)` | A function for custom validation. Return a string or an array of strings with the error message(s) if the value is invalid. Returning nothing, `null`, an empty string, or an empty array means the value is valid. Asynchronous functions are supported, but they do not prevent form submission when using `validationMode="onSubmit"`.                                               |
| `validationMode?`         | `"onBlur" \| "onChange" \| "onSubmit"`                                                                                             | Determines when the field should be validated. This takes precedence over the `validationMode` prop on `<Form>`. - `onSubmit`: triggers validation when the form is submitted, and re-validates on change after submission. - `onBlur`: triggers validation when the control loses focus. - `onChange`: triggers validation on every change to the control value. Default: `'onSubmit'` |
| `validationDebounceTime?` | `number`                                                                                                                           | How long to wait between `validate` callbacks if `validationMode="onChange"` is used. Specified in milliseconds. Default: `0`                                                                                                                                                                                                                                                           |
| `invalid?`                | `boolean`                                                                                                                          | Whether the field is invalid. Useful when the field state is controlled by an external library.                                                                                                                                                                                                                                                                                         |
| `dirty?`                  | `boolean`                                                                                                                          | Whether the field's value has been changed from its initial value. Useful when the field state is controlled by an external library.                                                                                                                                                                                                                                                    |
| `touched?`                | `boolean`                                                                                                                          | Whether the field has been touched. Useful when the field state is controlled by an external library.                                                                                                                                                                                                                                                                                   |
| `actionsRef?`             | `React.RefObject<FieldRootActions \| null>`                                                                                        | A ref to imperative actions. - `validate`: Validates the field when called.                                                                                                                                                                                                                                                                                                             |
| `orientation?`            | `"horizontal" \| "responsive" \| "vertical"`                                                                                       | Default: `"vertical"`                                                                                                                                                                                                                                                                                                                                                                   |

### FieldItem

| Prop        | Type      | Description                                                                                                                                    |
| ----------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `disabled?` | `boolean` | Whether the wrapped control should ignore user interaction. The `disabled` prop on `<Field.Root>` takes precedence over this. Default: `false` |

### FieldLabel

| Prop           | Type      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| -------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nativeLabel?` | `boolean` | Whether the component renders a native `<label>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a label (for example, `<div>`). This is useful to avoid inheriting label behaviors on `<button>` controls (such as `<Select.Trigger>` and `<Combobox.Trigger>`), including avoiding `:hover` on the button when hovering the label, and preventing clicks on the label from firing on the button. Default: `true` |

### FieldSeparator

| Prop           | Type                         | Description                                               |
| -------------- | ---------------------------- | --------------------------------------------------------- |
| `orientation?` | `"horizontal" \| "vertical"` | The orientation of the separator. Default: `"horizontal"` |

### FieldError

| Prop      | Type                                     | Description                                                                                                                                                                                                                                                      |
| --------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `match?`  | `boolean \| keyof ValidityState`         | Determines whether to show the error message according to the field's [ValidityState](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState). Specifying `true` will always show the error message, and lets external libraries control the visibility. |
| `errors?` | `({ message?: string; } \| undefined)[]` | Messages to show, such as the errors a form library returns. Takes the place of `children`.                                                                                                                                                                      |
