> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Form fields

> Bind Anvil2 input fields to formstate with the Form.* wrapper components.

The `Form.*` components bind Anvil2 input fields to a [`formstate`](https://formstate.github.io/) field state, removing the boilerplate of wiring value, change, error, and warning props by hand. Each wrapper is a MobX `observer`, so it re-renders when its bound `A2InputFieldState` changes.

## Installation

Form fields rely on `formstate`, `mobx`, and `mobx-react` as peer dependencies. Install them alongside the package:

```shell theme={null}
npm install @servicetitan/anvil2-ext-common formstate mobx mobx-react
```

## Usage

Create an `A2InputFieldState`, then pass it to the matching `Form.*` component. The wrapper reads `value`, reports validation `error`/`warning`, and writes changes back to the field state.

```tsx theme={null}
import { Form, A2InputFieldState } from "@servicetitan/anvil2-ext-common";

const name = new A2InputFieldState("");
name.validators((value) => !value && "Name is required");

function NameField() {
  return <Form.TextField fieldState={name} label="Name" />;
}
```

The field state is the source of truth. Read `name.value` to get the current value, call `name.validate()` to run validation, and check `name.hasError` / `name.error` to inspect the result. See [Field state](/docs/extended-libraries/common/form-fields/field-state) for the full API.

## Available wrappers

Each wrapper accepts a `fieldState` prop plus the underlying Anvil2 component's props. The field state's value type matches the field:

| Component                     | `fieldState` value type          |
| ----------------------------- | -------------------------------- |
| `Form.DateFieldRange`         | `{ startDate, endDate } \| null` |
| `Form.DateFieldSingle`        | `string \| null`                 |
| `Form.DateFieldYearless`      | `YearlessDate \| null`           |
| `Form.DateFieldYearlessRange` | `{ startDate, endDate }`         |
| `Form.NumberField`            | `number \| null`                 |
| `Form.Textarea`               | `string`                         |
| `Form.TextField`              | `string`                         |
| `Form.TimeField`              | `string \| null`                 |

## Selection fields

`Form.Select`, `Form.MultiSelect`, and `Form.TreeSelect` wrap the beta Anvil2 select components and ship from the package root alongside the rest of the namespace. Provide a `loadOptions` function and bind the selected option(s) to the field state:

```tsx theme={null}
import { Form, A2InputFieldState } from "@servicetitan/anvil2-ext-common";
import type { SelectFieldOption } from "@servicetitan/anvil2/beta";

const fruit = new A2InputFieldState<SelectFieldOption | null>(null);

const loadOptions = async (search: string) =>
  [
    { id: 1, label: "Apple" },
    { id: 2, label: "Banana" },
  ].filter((option) => option.label.toLowerCase().includes(search.toLowerCase()));

function FruitSelect() {
  return <Form.Select fieldState={fruit} label="Fruit" loadOptions={loadOptions} />;
}
```

`Form.Select` and `Form.MultiSelect` support every loader mode (eager, page-lazy, offset-lazy, and group-lazy) — pass the `loadOptions` function that matches your strategy.

## Group fields

Checkbox and radio inputs are provided as the group wrappers `Form.RadioGroup` and `Form.CheckboxGroup`. These render an Anvil2 group with a `FieldMessage`, so validation errors and warnings surface as text. Pass an `items` array; the field state holds the selected `id` (`Form.RadioGroup`) or `id`s (`Form.CheckboxGroup`).

```tsx theme={null}
import {
  Form,
  SelectableOptionsArrayFieldState,
} from "@servicetitan/anvil2-ext-common";

const items = [
  { id: 1, label: "Email" },
  { id: 2, label: "SMS" },
];

const channels = new SelectableOptionsArrayFieldState<number, (typeof items)[number]>(
  [],
);
channels.validators((value) => value.length === 0 && "Select at least one channel");

function ChannelGroup() {
  return (
    <Form.CheckboxGroup fieldState={channels} items={items} legend="Channels" />
  );
}
```

## Limitations

* Single checkbox and radio inputs have no standalone wrapper. Use `Form.CheckboxGroup` or `Form.RadioGroup` — a bare Anvil2 `Checkbox`/`Radio` exposes a boolean error (styling only) with no message area, so the group is the form pattern that renders validation messages.
* The following Anvil2 input types have no `Form.*` wrapper: `SearchField`, `InputMask`, `Switch`, `ButtonToggle`, `SelectCard`, `Combobox`, `SegmentedControl`, and `RichTextEditor`.
