# Field

One form control with its label, hint and error, wired together: the label points at the control, the control is described by the hint and the error and is `aria-invalid` while there is one, and `required` marks the label and sets `aria-required` (not the native `required`, so the browser's own bubbles don't speak over yours).

Wrap one control: an `Input`, `Textarea`, `Combobox`, `NumberInput`, a native `<select>`, anything that takes an `id` and `aria-*`. Field passes them in. For a control that needs them somewhere else, pass a function as `children`: it gets the props to spread and whether to ring. For a set of radios or checkboxes, set `group`: the field becomes a `fieldset` whose `legend` is the label, described by the hint and the error. Each radio and checkbox in it is `aria-invalid` while there is an error, and a required group's legend says "(required)" to screen readers. A function child of a group gets the same props, with its own id and no `aria-describedby` (the fieldset carries both).

Errors are the app's. Field shows the one you pass and never makes one up, in a polite live region, so one that appears or changes while the person works is read out without moving focus. To point at the first problem after a submit, pass the form's submit count as `attempt` (react-hook-form's `formState.submitCount`, or a counter your server action returns with its errors). When it goes up and this is the first field in its `<form>` with an error, focus moves to the control and an `Input` or `Textarea` rings once. Change `attempt` in the same render as the errors it found; a field that only gains an error later, as the person types, is left alone. Errors that arrive with a submit are not put in the live region, since the focus move and a `FormErrorSummary` read them; without `attempt`, Field can't tell a submit's errors apart, and each is read out as it appears.

- Registry name: `field` (`@candor/field`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/field

## Install

```bash
npx candor-ui add field
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/field`.
It brings in: label, locale, motion, tokens, utils.

## Example

```tsx
"use client"

import * as React from "react"
import { Field } from "@/components/candor/ui/field"
import { Input } from "@/components/candor/ui/input"
import { Textarea } from "@/components/candor/ui/textarea"
import { Button } from "@/components/candor/ui/button"

type Errors = { name?: string | undefined; email?: string | undefined }

// The app checks the values and says what is wrong; Field shows it, wires it to the control, and on a failed submit
// moves focus to the first field with a problem.
export default function Example() {
  const [errors, setErrors] = React.useState<Errors>({})
  const [attempt, setAttempt] = React.useState(0)
  return (
    <form
      noValidate
      className="flex w-full max-w-sm flex-col gap-4"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        const next: Errors = {}
        if (!String(data.get("name")).trim()) next.name = "Enter a name for the agent"
        if (!/^\S+@\S+$/.test(String(data.get("email")))) next.email = "Enter an email address, like name@company.com"
        setErrors(next)
        setAttempt((n) => n + 1)
      }}
    >
      <Field label="Agent name" required error={errors.name} attempt={attempt}>
        <Input name="name" />
      </Field>
      <Field label="Escalation email" hint="Where the agent sends what it can't decide." required error={errors.email} attempt={attempt}>
        <Input name="email" type="email" autoComplete="email" />
      </Field>
      <Field label="Notes" optional>
        <Textarea name="notes" rows={3} />
      </Field>
      <Button type="submit" className="self-start">
        Save
      </Button>
    </form>
  )
}
```

## <Field>

Props:

- `label: React.ReactNode`: What the control is for. Shown above it; for a `group`, the legend.
- `hint?: React.ReactNode | undefined`: Help that stays: the format wanted, what the value is used for. Read out with the control.
- `error?: string | null | undefined`: What is wrong with the value, in words that say how to fix it ("Enter an email address, like name@company.com"). Nothing, or an empty string, is no error.
- `required?: boolean | undefined` (default `false`): The field must be filled. Marks the label (the mark is hidden from screen readers, which hear `aria-required`).
- `optional?: boolean | undefined` (default `false`): Says "(optional)" after the label. Use it when most of the form is required, in place of marking every required field.
- `attempt?: number | undefined`: How many times the form has been submitted. When it goes up and this is the form's first field with an error, focus moves here and the control rings once. The errors that arrive with it are shown but not announced, so they are read once, by the summary and the focus move.
- `group?: boolean | undefined` (default `false`): The control is a set of radios or checkboxes: render a `fieldset` with a `legend` in place of a `label`.
- `id?: string | undefined`: The control's id, for a `form-error-summary` link to point at. Defaults to a generated one, or the control's own `id`.
- `className?: string | undefined`: Classes for the field's outer box.
- `children: ControlElement | React.ReactNode | ((control: FieldControlProps, state: { attend: boolean }) => React.ReactNode)`: The control, or a function that renders it from the props to spread and whether it should ring. A `group` takes any content: its radios or checkboxes with their labels.

## Strings (`field`)

The fixed words, English by default. Override any of them under `field` in `<LocaleProvider strings={{ field: { … } }}>`; the table is exported as `fieldStrings`.

- `requiredMark`: *
- `optional`: (optional)
- `error`: Error:
- `required`: (required)

## Types

### FieldControlProps

What a field hands its control: the wiring that ties the label, hint and error to it. Spread it on the control.

```ts
{
  /** The control's id, which the label's `htmlFor` (and a `form-error-summary` link) points at. */
  id: string;
  /** The hint's and the error's ids, after any the control already had. */
  "aria-describedby": string | undefined;
  /** True while the field has an error. */
  "aria-invalid": true | undefined;
  /** True when the field is `required`. */
  "aria-required": true | undefined;
  /** Clears the ring once it has played. */
  onAnimationEnd: React.AnimationEventHandler;
}
```

## Motion

- **error** (arrive, fade): The error message fades in under its control when it appears after the field was on screen, and again when its text changes. An error already showing when the field first draws is still, and the ring on the control is the control's own (`Input`'s attention:invalid).

Set with `--cd-motion-field-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
