<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Field

A labeled form control: the label, an optional description, the control and its error, wired together with ids that match between the server and the browser. Every Ouro form control takes these props and is built on the same parts.

```tsx
import { Field } from "@/components/ouro/field/field";
```

## Example

```tsx
<Field label="Email" name="email" required description="We never share it." error={errors?.email}>
  <FieldControl type="email" autoComplete="email" />
</Field>
// Helper text after the control: a FieldDescription placed where it shows.
<Field label="Password" name="password" required error={errors?.password}>
  <FieldControl type="password" autoComplete="new-password" />
  <FieldDescription>Use 8 or more characters.</FieldDescription>
</Field>
```

## Usage

Pass `error` straight from a server action or a client check: it shows the message and sets `aria-invalid`. The error only shows: it never blocks the form's next submit, so the user can always send the form again (only the browser's own checks, such as `required`, block). Inside a `Form`, a field with a `name` shows the form's error for that name when it has no `error` of its own. The control is `FieldControl`, from `field-parts`.

The order is fixed: label, `description`, control, error. To put helper text after the control, leave `description` out and render a `FieldDescription` after the `FieldControl`. It is linked to the control (`aria-describedby`) wherever it sits, and the error still comes last. `TextField` and the other one-tag controls render their own control, so build that field from `Field` and `FieldControl`.

The required marker is drawn with CSS beside the label and is not part of its text, so a test finds the field by the label alone: `getByLabelText("Email")`, or Playwright's `getByLabel("Email", { exact: true })`. The marker stays visible in forced colors.

A field's error is the control's description, not an alert, so screen readers read it when focus reaches the field (a `Form` moves focus to the first invalid one). In a test, assert it with `toHaveAccessibleDescription("Enter an email address.")` or `getByText`; `getByRole("alert")` finds only a Form's message about the whole form.

## Use instead when

- Use `Fieldset` instead to group several controls under one legend, and the `FieldRoot` parts when the label, control and error need another layout.

## Props

Type: `FieldProps`.

| Prop | Type | Description |
| --- | --- | --- |
| `children` (required) | `ReactNode` | The control: `<FieldControl />`, or `<FieldControl render={<textarea />} />` for another element. |
| `className` | `string` | Layout only: margins, width, grid placement (`mt-4`, `col-span-2`). Appearance classes warn in development; style fields in your theme. |
| `description` | `ReactNode` | Supporting text under the label, read after the label through `aria-describedby`. |
| `disabled` | `boolean` | Disables the control and fades the field. |
| `error` | `ReadonlyErrorMessages` | What is wrong, from your server action or client check. A message (or several, as an array, each shown once as a list item) shows under the control, which gets `aria-invalid` and points at the message with `aria-describedby`. A message is text or a React node, so it can carry a link: `error={<>Already imported. <Link href="/recipes/soup">Open it</Link></>}`. An empty string or array means valid. It only shows: it never blocks the next submit of the form around it. |
| `hideLabel` | `boolean` | Hides the label visually; screen readers still read it as the control's name. |
| `label` (required) | `string` | What the control is for, shown above it and read as its name. Required: every control needs a name. To hide it visually, keep it and add `hideLabel`. |
| `name` | `string` | The form field name the control submits under. A `Form`'s `errors` for this name show here when there is no `error` prop, and go away once the user changes the control. |
| `required` | `boolean` | The control must be filled: it gets `required` and `aria-required`, and the label a marker. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `errorMessage`: Not a prop. Ouro UI uses `error`: `<Field error="Required.">`.
- `helperText`: Not a prop. Ouro UI uses `description`: `<Field description="We never share it.">`.
- `htmlFor`: Not a prop. Field connects the label to its control itself: `<Field label="Email"><FieldControl /></Field>`.
- `invalid`: Not a prop. `error` marks the field invalid: `<Field error="What is wrong">`.
- `isDisabled`: Not a prop. Ouro UI uses `disabled`: `<Field disabled>`.
- `isInvalid`: Not a prop. `error` marks the field invalid: `<Field error="What is wrong">`.
- `isRequired`: Not a prop. Ouro UI uses `required`: `<Field required>`.
- `placeholder`: Not a prop, and not a label. Pass `label`, and a placeholder example on the control: `<FieldControl placeholder="you@example.com" />`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: Field merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Parts

When these props cannot express your layout, build it from the parts in `@/components/ouro/field/field-parts`. See [parts.md](parts.md#field-parts) for the required nesting.

## Types

### `ErrorMessage`

One error message: text, or a React element for a message that carries a link or other markup.

```ts
type ErrorMessage = string | ReactElement;
```

### `ReadonlyErrorMessages`

A read-only message view accepted by fields and Form; mutable input arrays remain valid.

```ts
type ReadonlyErrorMessages = ErrorMessage | readonly ErrorMessage[];
```

[^layout-classes]: test:registry/ouro/components/ouro/field/field.test.tsx::Field wiring > lets app layout classes win verify:testExists(registry/ouro/components/ouro/field/field.test.tsx::Field wiring > lets app layout classes win)
