# Field

Lays out a control with its label, description and error, and groups fields into sets.

- **Import:** `import { Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegend, FieldSeparator, FieldSet, FieldContent, FieldTitle } from "@booleanpress/ui/field"`
- **Page:** <https://ui.booleanpress.com/components/field> · @booleanpress/ui 0.1.0

## Usage

`Field` wraps one control. Put `FieldLabel`, the control, `FieldDescription` and `FieldError` inside it. Group several fields with `FieldGroup`, and wrap a related set in `FieldSet` with a `FieldLegend`.

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

export function Port() {
  return (
    <Field data-invalid="true">
      <FieldLabel htmlFor="port">Port</FieldLabel>
      <Input id="port" aria-invalid aria-describedby="port-help port-error" />
      <FieldDescription id="port-help">Usually 587.</FieldDescription>
      <FieldError id="port-error">Use a port between 1 and 65535.</FieldError>
    </Field>
  )
}
```

`Field` only lays out and styles: it does not connect anything. You set `htmlFor`/`id`, `aria-invalid` and `aria-describedby` yourself. `data-invalid="true"` turns the label and text destructive; `data-disabled="true"` dims the label and description. `orientation` is `vertical` (default), `horizontal` or `responsive` (horizontal once the `FieldGroup` is 28 rem wide). `FieldError` takes children, or `errors`, an array of `{ message }` that it de-duplicates (one message as text, several as a list). `FieldContent` stacks a label and description beside a checkbox or switch; `FieldTitle` is label-styled text that is not a label; `FieldSeparator` draws a rule, with optional text.

## Examples

### Basic

A label, an input and a description, tied together with `htmlFor` and `aria-describedby`.

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

export default function FieldBasic() {
  return (
    <Field className="max-w-sm">
      <FieldLabel htmlFor="reply-to">Reply-to address</FieldLabel>
      <Input id="reply-to" type="email" placeholder="support@example.com" aria-describedby="reply-to-help" />
      <FieldDescription id="reply-to-help">Replies to your emails go here.</FieldDescription>
    </Field>
  )
}
```

### Error

`data-invalid` colours the field; `FieldError` is announced as an alert and read with the name.

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

export default function FieldErrorExample() {
  return (
    <Field className="max-w-sm" data-invalid="true">
      <FieldLabel htmlFor="smtp-port">Port</FieldLabel>
      <Input id="smtp-port" defaultValue="99999" aria-invalid aria-describedby="smtp-port-help smtp-port-error" />
      <FieldDescription id="smtp-port-help">Usually 587.</FieldDescription>
      <FieldError id="smtp-port-error">Use a port between 1 and 65535.</FieldError>
    </Field>
  )
}
```

### Horizontal

A checkbox beside its label and description, aligned with `FieldContent`.

```tsx
import { Checkbox } from "@booleanpress/ui/checkbox"
import { Field, FieldContent, FieldDescription, FieldLabel } from "@booleanpress/ui/field"

export default function FieldHorizontal() {
  return (
    <Field orientation="horizontal" className="max-w-sm">
      <Checkbox id="bounce-alerts" defaultChecked aria-describedby="bounce-alerts-help" />
      <FieldContent>
        <FieldLabel htmlFor="bounce-alerts">Alert me about bounces</FieldLabel>
        <FieldDescription id="bounce-alerts-help">Sent once an hour, at most.</FieldDescription>
      </FieldContent>
    </Field>
  )
}
```

### Group and set

`FieldSet` and `FieldLegend` name the group; `FieldGroup` spaces the fields; `FieldSeparator` divides them.

```tsx
import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet } from "@booleanpress/ui/field"
import { Input } from "@booleanpress/ui/input"

export default function FieldGroupExample() {
  return (
    <FieldSet className="w-full max-w-sm">
      <FieldLegend>Connection</FieldLegend>
      <FieldDescription>Where BooleanSMTP sends your email.</FieldDescription>
      <FieldGroup>
        <Field>
          <FieldLabel htmlFor="group-host">Host</FieldLabel>
          <Input id="group-host" defaultValue="smtp.example.com" />
        </Field>
        <Field>
          <FieldLabel htmlFor="group-port">Port</FieldLabel>
          <Input id="group-port" defaultValue="587" />
        </Field>
        <FieldSeparator>Sign-in</FieldSeparator>
        <Field>
          <FieldLabel htmlFor="group-user">Username</FieldLabel>
          <Input id="group-user" defaultValue="apikey" />
        </Field>
      </FieldGroup>
    </FieldSet>
  )
}
```

### Disabled

`data-disabled` dims the label and the description; `disabled` on the control stops input.

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

export default function FieldDisabled() {
  return (
    <Field className="max-w-sm" data-disabled="true">
      <FieldLabel htmlFor="locked-domain">Sending domain</FieldLabel>
      <Input id="locked-domain" defaultValue="mail.example.com" disabled aria-describedby="locked-domain-help" />
      <FieldDescription id="locked-domain-help">Verified domains cannot be changed.</FieldDescription>
    </Field>
  )
}
```

## Accessibility

**Semantics.** `Field` is a `div` with `role="group"`; `FieldSet` is a `fieldset` and `FieldLegend` a `legend`, so a set is named by its legend. `FieldError` has `role="alert"`, so it is announced when it appears.

**Labels.** Name each control with `FieldLabel htmlFor`. Give an error and a description an `id` and list them in the control's `aria-describedby`. Name a set with `FieldLegend`.

**Focus.** Nothing in a field takes focus except the control. The label forwards a click to it.

**Known limits.**

- A `Field`'s `role="group"` has no name of its own; only a `FieldSet` takes its name from the legend.
- `data-invalid` and `data-disabled` change colour only. Set `aria-invalid` and `disabled` on the control.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### Field

Renders a `div` and passes it every other prop.

### FieldLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### FieldDescription

Renders a `p` and passes it every other prop.

### FieldError

Renders a `div` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `errors` | `({ message?: string; })[] \| undefined` |  |  |

### FieldGroup

Renders a `div` and passes it every other prop.

### FieldLegend

Renders a `legend` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"label" \| "legend"` | `legend` | `legend` is the larger group title; `label` is the size of a field label. |

### FieldSeparator

Renders a `div` and passes it every other prop.

### FieldSet

Renders a `fieldset` and passes it every other prop.

### FieldContent

Renders a `div` and passes it every other prop.

### FieldTitle

Renders a `div` and passes it every other prop.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="field"` (Field), `data-slot="field-label"` (FieldLabel), `data-slot="field-description"` (FieldDescription), `data-slot="field-error"` (FieldError), `data-slot="field-group"` (FieldGroup), `data-slot="field-legend"` (FieldLegend), `data-slot="field-separator"` (FieldSeparator), `data-slot="field-set"` (FieldSet), `data-slot="field-content"` (FieldContent), `data-slot="field-label"` (FieldTitle), and `data-variant`, `data-orientation`, `data-content`.

## Theming

| Token | Used for |
| --- | --- |
| `--background` | background |
| `--destructive` | text |
| `--muted-foreground` | text |
| `--primary` | border, background, text |
