# Field

> A label, description, error and required marker around any control, with the ids and aria attributes wired for you.

Source: https://platform.azmara.io/docs/components/field

Group: Inputs · Status: new · Added in @azmr/ui 0.5.0

## Usage

```tsx
import { Field } from "@azmr/ui/field";
import { Input, Textarea } from "@azmr/ui/input";
```

```tsx
import { Field } from "@azmr/ui/field";
import { Input, Textarea } from "@azmr/ui/input";

export default function FieldBasic() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <Field label="Business name" required>
        <Input autoComplete="organization" />
      </Field>
      <Field label="GST number" description="Nine digits, as on your IRD letter.">
        <Input inputMode="numeric" placeholder="123-456-789" />
      </Field>
      <Field label="Notes" description="Printed at the bottom of every invoice.">
        <Textarea placeholder="Thanks for your business." />
      </Field>
    </div>
  );
}
```

`Field` wraps one control. It gives the control an `id`, points `aria-labelledby` at the label and `aria-describedby` at the description, and keeps an `id` you set yourself. It works with `Input`, `Textarea` and any Base UI control.

## Errors

```tsx
"use client";

import { Button } from "@azmr/ui/button";
import { Field } from "@azmr/ui/field";
import { Input } from "@azmr/ui/input";
import { type FormEvent, useState } from "react";

function emailError(value: string): string | undefined {
  if (value.trim() === "") return "Enter your email address";
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
    return "Enter an email address like name@example.co.nz";
  return undefined;
}

export default function FieldValidation() {
  const [email, setEmail] = useState("");
  const [error, setError] = useState<string>();
  const [sent, setSent] = useState(false);

  function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const next = emailError(email);
    setError(next);
    setSent(next === undefined);
  }

  return (
    <form noValidate onSubmit={onSubmit} className="flex w-full max-w-sm flex-col gap-4">
      <Field label="Email" description="We send receipts here." required error={error}>
        <Input
          type="email"
          autoComplete="email"
          value={email}
          onValueChange={(value) => {
            setEmail(value);
            setSent(false);
            if (error) setError(emailError(value));
          }}
        />
      </Field>
      <div className="flex items-center gap-3">
        <Button type="submit">Save</Button>
        <p role="status" className="text-sm text-muted-foreground">
          {sent ? "Saved." : ""}
        </p>
      </div>
    </form>
  );
}
```

Pass the message as `error`. The field is then invalid: the control gets `aria-invalid="true"`, `aria-errormessage` and `aria-describedby` point at the message, and the border turns red. `undefined`, `null`, `false` and `""` all mean no error.

The message sits in a polite live region that is in the page before any error arrives, so screen readers announce it when it appears without moving focus. Submit the form above while it is empty, then type a valid address.

`required` shows a marker after the label and sets `aria-required` on the control. The marker is hidden from screen readers, which hear "required" from the control instead. It doesn't turn on the browser's own validation, so add `noValidate` to the form and validate it yourself, or use the field's `validate` prop.

## With react-hook-form

`Input` and `Textarea` forward the real element's `ref`, so `register()` works without a `Controller`. Pass `errors.<name>?.message` as `error`:

```tsx
import { useForm } from "react-hook-form";
import { Button } from "@azmr/ui/button";
import { Field } from "@azmr/ui/field";
import { Input, Textarea } from "@azmr/ui/input";

type Values = { email: string; notes: string };

export function InvoiceForm({ onSave }: { onSave: (values: Values) => void }) {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Values>();

  return (
    <form noValidate onSubmit={handleSubmit(onSave)}>
      <Field label="Email" required error={errors.email?.message}>
        <Input type="email" {...register("email", { required: "Enter your email address" })} />
      </Field>
      <Field label="Notes" description="Printed on the invoice.">
        <Textarea {...register("notes")} />
      </Field>
      <Button type="submit">Save</Button>
    </form>
  );
}
```

## Built-in validation

Without an `error` prop, the field shows its own messages: the result of `validate`, or the browser's message for constraints such as `type="email"`. `validationMode` sets when it checks (`onSubmit` by default, or `onBlur` or `onChange`).

```tsx
<Field
  label="Discount code"
  validationMode="onBlur"
  validate={(value) => (String(value).length < 4 ? "Codes are at least 4 characters" : null)}
>
  <Input />
</Field>
```

## Any control

`Field` wires its label, description and error to `Input`, `Textarea` and the other Base UI controls by itself. For any other control, such as a Radix (shadcn) Select trigger, a native `<select>` or a date picker from another library, pass a function as the child. It gets the `id`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, `aria-required` and `aria-errormessage` to spread on the element that takes focus:

```tsx
<Field label="Country" description="Where the invoice is sent." error={errors.country?.message}>
  {(control) => (
    <select {...control} {...register("country")}>
      <option>New Zealand</option>
      <option>Australia</option>
    </select>
  )}
</Field>
```

With React Hook Form, `FormField`'s `render` passes the same props as `control`. See [Forms](https://platform.azmara.io/docs/guide/forms.md#formfield).

## Custom layouts

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

export default function FieldParts() {
  return (
    <div className="w-full max-w-sm">
      <FieldRoot>
        <div className="flex items-baseline justify-between">
          <FieldLabel required>Password</FieldLabel>
          <a
            href="#forgot-password"
            className="text-sm text-primary underline-offset-4 hover:underline"
          >
            Forgot password?
          </a>
        </div>
        <Input type="password" autoComplete="current-password" />
        <FieldDescription>At least 12 characters.</FieldDescription>
      </FieldRoot>
    </div>
  );
}
```

For a layout `Field` doesn't cover, compose the parts: `FieldRoot`, `FieldLabel`, `FieldDescription` and `FieldError`. The wiring is the same. Pass `match` to `FieldError` to show it whenever the field is invalid, for example when another library owns validation.

## Props

| Prop             | Type                                                                                                                                            | Default      | Required | Description                                                                                                                                                                                                                                                                                                                                                       |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `disabled`       | `boolean \| undefined`                                                                                                                          | `false`      | No       | Whether the component should ignore user interaction. Takes precedence over the `disabled` prop on the `<Field.Control>` component.                                                                                                                                                                                                                               |
| `name`           | `string`                                                                                                                                        |              | No       | 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>) \| undefined` |              | No       | 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" \| undefined`                                                                                             | `'onSubmit'` | No       | 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. |
| `invalid`        | `boolean \| undefined`                                                                                                                          |              | No       | Whether the field is invalid. Useful when the field state is controlled by an external library.                                                                                                                                                                                                                                                                   |
| `className`      | `string`                                                                                                                                        |              | No       |                                                                                                                                                                                                                                                                                                                                                                   |
| `label`          | `ReactNode`                                                                                                                                     |              | No       |                                                                                                                                                                                                                                                                                                                                                                   |
| `description`    | `ReactNode`                                                                                                                                     |              | No       |                                                                                                                                                                                                                                                                                                                                                                   |
| `error`          | `ReactNode`                                                                                                                                     |              | No       | The error to show, for example `errors.email?.message` from React Hook Form. Any value other than `undefined`, `null`, `false` or `""` marks the field invalid.                                                                                                                                                                                                   |
| `required`       | `boolean \| undefined`                                                                                                                          |              | No       | Shows the required marker and sets `aria-required` on the control.                                                                                                                                                                                                                                                                                                |
| `children`       | `((control: FieldControlProps) => React.ReactNode) \| React.ReactNode`                                                                          |              | Yes      | One control: `Input`, `Textarea` or any Base UI control. For any other control, such as a Radix Select trigger, a native `<select>` or a third-party picker, pass a function: it gets the `id` and ARIA props to spread on the control, so the label, description and error are wired to it too.                                                                  |

`disabled` on the field disables its control. `name` takes precedence over the control's own `name` when the form is submitted.

## Accessibility

- The label is a real `<label>`, so clicking it focuses the control.
- Errors are exposed through `aria-invalid` and `aria-errormessage`, and read with the field through `aria-describedby`, never by colour alone.
- New errors are announced politely through a live region, without moving focus.
- The required marker is decorative; the control itself carries `aria-required`.

| Key              | Action                                                                          |
| ---------------- | ------------------------------------------------------------------------------- |
| `Tab`            | Moves focus to the control. The label, description and error are not focusable. |
| `Shift` or `Tab` | Moves focus to the previous control.                                            |
