# Field

Lays out a form control with its label, description and error message, and groups fields into sections.

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

function FieldDemo() {
  return (
    <Field className="max-w-sm">
      <FieldLabel htmlFor="tenant-name">Tenant name</FieldLabel>
      <Input id="tenant-name" placeholder="Acme Corporation" />
      <FieldDescription>Shown in reports and alert emails.</FieldDescription>
    </Field>
  )
}
```

## Usage

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

<Field>
  <FieldLabel htmlFor="tenant-name">Tenant name</FieldLabel>
  <Input id="tenant-name" />
  <FieldDescription>Shown in reports and alert emails.</FieldDescription>
</Field>
```

Wrap every form control in a `Field`: [Input](/components/input),
[Select](/components/select), [Checkbox](/components/checkbox),
[Radio Group](/components/radio-group), [Slider](/components/slider) and the rest. The field
sets the spacing between label, control and help text. With `data-disabled` it dims the
label; with `data-invalid` it colors the label and help text red.

`Field` is layout only. It does not create ids or validate: give the control an `id`, point
`FieldLabel` at it with `htmlFor`, and decide in your form code when a value is invalid.

| Part               | What it is                                                         |
| ------------------ | ------------------------------------------------------------------ |
| `Field`            | One control with its label and help text.                          |
| `FieldLabel`       | The `<label>` of the control.                                      |
| `FieldDescription` | Help text under the control, or under a legend.                    |
| `FieldError`       | The error message. Renders nothing when it has no content.         |
| `FieldContent`     | Stacks a label and a description next to a checkbox or radio.      |
| `FieldTitle`       | A label-styled heading for a field that is not a single control.   |
| `FieldSet`         | A `<fieldset>` that groups related fields.                         |
| `FieldLegend`      | The title of a `FieldSet`. `variant="label"` makes it label-sized. |
| `FieldGroup`       | Stacks fields with the standard gap. Needs a definite width.       |
| `FieldSeparator`   | A divider between groups of fields, with optional text.            |

## Examples

### Invalid

Set `data-invalid` on the `Field` and `aria-invalid` on the control. The label turns red, the
control gets the error ring, and `FieldError` shows the message. Write the message as the fix
("Enter a domain without the protocol"), not as the fault.

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

function FieldInvalid() {
  return (
    <Field data-invalid className="max-w-sm">
      <FieldLabel htmlFor="monitored-domain">Monitored domain</FieldLabel>
      <Input
        id="monitored-domain"
        defaultValue="https://acme"
        aria-invalid
        aria-describedby="monitored-domain-error"
      />
      <FieldError id="monitored-domain-error">
        Enter a domain without the protocol, such as acme.com.
      </FieldError>
    </Field>
  )
}
```

`FieldError` also takes `errors`, an array of `{ message }` objects in the shape form libraries
return. It removes duplicates and renders a list when more than one message remains.

### Horizontal

`orientation="horizontal"` puts the control and the label on one line, for checkboxes, radios
and switches. Put the label and description in a `FieldContent` so they stack.
`orientation="responsive"` is vertical by default and turns horizontal when the surrounding
`FieldGroup` is at least 28rem wide (a container query, not the viewport).

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

function FieldHorizontal() {
  return (
    <Field orientation="horizontal" className="max-w-sm">
      <Checkbox id="include-subdomains" defaultChecked />
      <FieldContent>
        <FieldLabel htmlFor="include-subdomains">Include subdomains</FieldLabel>
        <FieldDescription>
          Scan every subdomain found in DNS and certificate logs.
        </FieldDescription>
      </FieldContent>
    </Field>
  )
}
```

### A full form

`FieldSet` and `FieldLegend` name a group of fields; `FieldGroup` spaces them; `FieldSeparator`
divides them. A horizontal `Field` also lays out the form buttons.

`FieldGroup` is a size container (the container that `orientation="responsive"` queries), so it
takes no width from its content. Give it a definite width: the width of the form column, or a
class such as `w-full`, `w-56` or `max-w-sm`. With `w-fit` it collapses to zero and every label
wraps one word per line. For the same reason, a `Field` placed in a row next to other fields
(a `flex` row of radios, for example) needs `className="w-auto"`: by default it is `w-full`
and shares the row instead of fitting its label.

```tsx
import { Button } from "@zenoxai/ui/components/button"
import { Checkbox } from "@zenoxai/ui/components/checkbox"
import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet } from "@zenoxai/ui/components/field"
import { Input } from "@zenoxai/ui/components/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@zenoxai/ui/components/select"

function FieldForm() {
  const channels = [
    { label: "Email", value: "email" },
    { label: "Slack", value: "slack" },
    { label: "Webhook", value: "webhook" },
  ]

  return (
    <form
      className="w-full max-w-md"
      onSubmit={(event) => event.preventDefault()}
    >
      <FieldSet>
        <FieldLegend>Notification rule</FieldLegend>
        <FieldDescription>
          Sends a message when an alert matches the rule.
        </FieldDescription>
        <FieldGroup>
          <Field>
            <FieldLabel htmlFor="rule-name">Name</FieldLabel>
            <Input id="rule-name" placeholder="Critical alerts to on-call" />
          </Field>
          <Field>
            <FieldLabel htmlFor="rule-channel">Channel</FieldLabel>
            <Select items={channels} defaultValue="email">
              <SelectTrigger id="rule-channel" className="w-full">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {channels.map((channel) => (
                  <SelectItem key={channel.value} value={channel.value}>
                    {channel.label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </Field>
          <FieldSeparator />
          <Field orientation="horizontal">
            <Checkbox id="rule-digest" />
            <FieldContent>
              <FieldLabel htmlFor="rule-digest">Send a daily digest</FieldLabel>
              <FieldDescription>
                One message at 09:00 instead of one per alert.
              </FieldDescription>
            </FieldContent>
          </Field>
          <Field orientation="horizontal">
            <Button type="submit">Save rule</Button>
            <Button type="button" variant="outline">
              Cancel
            </Button>
          </Field>
        </FieldGroup>
      </FieldSet>
    </form>
  )
}
```

For selectable cards, wrap a horizontal `Field` in a `FieldLabel`. See the choice cards in
[Radio Group](/components/radio-group#choice-cards).

## Accessibility

- `FieldLabel` is a native `<label>`. It names the control only through `htmlFor` pointing at
  the control's `id`, or by wrapping the control.
- `FieldDescription` and `FieldError` are not linked to the control automatically. Give them
  an `id` and list it in the control's `aria-describedby` so screen readers read them with the
  control.
- `FieldError` has `role="alert"`, so it is announced as soon as it appears. Render it only
  after the user submits or leaves the field, not on every keystroke.
- `data-invalid` on the `Field` only changes styles. `aria-invalid` on the control is what
  assistive technology reads.
- Every `Field` renders `role="group"`. Use `FieldSet` and `FieldLegend` when a group of fields
  needs a name.

## API reference

`import { ... } from "@zenoxai/ui/components/field"`

### Field

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| orientation | `"horizontal" \| "vertical" \| "responsive" \| null` | `vertical` | `vertical` (default) puts the label above the control; `horizontal` puts them on one row, as for a checkbox or a switch; `responsive` is vertical until its FieldGroup is 28rem (448px) wide, then horizontal. |

### FieldLabel

No props of its own.

### FieldDescription

No props of its own.

### FieldError

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| errors | `({ message?: string; })[] \| undefined` |  | Error objects, such as react-hook-form's. Duplicate messages are dropped; one message renders as text, several as a list. Children win over it. |

### FieldGroup

No props of its own.

### FieldLegend

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| variant | `"label" \| "legend"` | `legend` | `legend` (default) is a section heading at text-base; `label` has the size of a FieldLabel, for a set that reads as one question, such as a radio group. |

### FieldSeparator

No props of its own.

### FieldSet

No props of its own.

### FieldContent

No props of its own.

### FieldTitle

No props of its own.

