

<Preview name="BasicFormExample" />

## Overview [#overview]

The `Form` components bind TanStack Form state to Cubitt field primitives. The form chrome handles labels, descriptions, errors, context, and submission wiring while the underlying input, select, checkbox, switch, and related components keep ownership of their own surfaces, borders, and interaction states.

<Callout type="warn" title="Use FormBuilder for most cases">
  For most use cases, use [Form Builder](/composites/form-builder) instead of raw form components. It renders forms from typed `formDefs` with built-in conditional logic, multi-step wizards, and automatic layout.
</Callout>

## Usage [#usage]

```tsx
import { Button } from "@tilt-legal/cubitt-components/button";
import {
  Form,
  InputField,
} from "@tilt-legal/cubitt-components/form";
import { revalidateLogic, useForm } from "@tanstack/react-form";
import { z } from "zod";
```

```tsx
const schema = z.object({
  email: z.email("Invalid email address"),
  password: z.string().min(8, "Password must be at least 8 characters"),
});

const form = useForm({
  defaultValues: {
    email: "",
    password: "",
  },
  validationLogic: revalidateLogic({
    mode: "submit",
    modeAfterSubmission: "change",
  }),
  validators: { onDynamic: schema },
  onSubmit: async ({ value }) => {
    console.log("Form submitted:", value);
  },
});
```

```tsx
return (
  <Form form={form} className="max-w-md">
    <InputField
      name="email"
      label="Email"
      type="email"
      placeholder="m@example.com"
      required
    />
    <InputField
      name="password"
      label="Password"
      type="password"
      placeholder="Password"
      required
    />
    <Button type="submit">Login</Button>
  </Form>
);
```

## Form Components [#form-components]

### Form [#form]

The root form component provides context, handles submit events, and exposes useful state attributes.

```tsx
<Form form={form} invalidAnimation="shake">
  {/* form fields */}
</Form>
```

| Prop               | Type              | Description                                     |
| ------------------ | ----------------- | ----------------------------------------------- |
| `form`             | `AnyReactFormApi` | TanStack Form instance from `useForm()`         |
| `invalidAnimation` | `"shake"`         | Optional shake animation when form is invalid   |
| `invalidOverride`  | `boolean`         | Override the computed invalid state             |
| `initialValidate`  | `boolean`         | Run validation on initial mount                 |
| `noValidate`       | `boolean`         | Disable browser validation. Defaults to `true`. |
| `className`        | `string`          | Layout classes for the form element             |

### FormField [#formfield]

Low-level wrapper for field content and error messages. Most fields include this automatically.

```tsx
import { Input } from "@tilt-legal/cubitt-components/input";
import { FormField } from "@tilt-legal/cubitt-components/form";

<FormField fieldName="username" messages={errors}>
  <Input id="username" aria-invalid={errors.length > 0} />
</FormField>
```

| Prop        | Type                           | Description        |
| ----------- | ------------------------------ | ------------------ |
| `messages`  | `string[]`                     | Error messages     |
| `fieldName` | `string`                       | Field name for IDs |
| `size`      | `"xs" \| "sm" \| "md" \| "lg"` | Message size       |
| `className` | `string`                       | Layout classes     |

### FormFieldSet [#formfieldset]

Groups related fields with an optional legend.

```tsx
<FormFieldSet title="Personal information" fieldName="personal">
  <InputField name="firstName" label="First name" />
  <InputField name="lastName" label="Last name" />
</FormFieldSet>
```

| Prop        | Type                           | Description        |
| ----------- | ------------------------------ | ------------------ |
| `title`     | `React.ReactNode`              | Fieldset legend    |
| `messages`  | `string[]`                     | Group-level errors |
| `fieldName` | `string`                       | Field name for IDs |
| `size`      | `"xs" \| "sm" \| "md" \| "lg"` | Message size       |
| `className` | `string`                       | Layout classes     |

## Context Hook [#context-hook]

Use `useTanstackFormContext` when a nested component needs access to the current form instance.

```tsx
import { Button } from "@tilt-legal/cubitt-components/button";
import { useTanstackFormContext } from "@tilt-legal/cubitt-components/form";
import { useStore } from "@tanstack/react-form";

function SubmitButton() {
  const form = useTanstackFormContext();
  const canSubmit = useStore(form.store, (state) => state.canSubmit);

  return (
    <Button type="submit" disabled={!canSubmit}>
      Save
    </Button>
  );
}
```

## Examples [#examples]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="BasicFormExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/button";
    import { Card, CardContent } from "@tilt-legal/cubitt-components/card";
    import {
      Form,
      InputField,
    } from "@tilt-legal/cubitt-components/form";
    import { revalidateLogic, useForm } from "@tanstack/react-form";
    import { z } from "zod";
    ```

    ```tsx
    const schema = z.object({
      email: z.email("Invalid email address"),
      password: z.string().min(8, "Password must be at least 8 characters"),
    });

    export function BasicFormExample() {
      const form = useForm({
        defaultValues: {
          email: "",
          password: "",
        },
        validationLogic: revalidateLogic({
          mode: "submit",
          modeAfterSubmission: "change",
        }),
        validators: { onDynamic: schema },
        onSubmit: async ({ value }) => {
          console.log(value);
        },
      });

      return (
        <Form className="max-w-sm" form={form} invalidAnimation="shake">
          <Card>
            <CardContent>
              <InputField label="Email" name="email" type="email" />
              <InputField label="Password" name="password" type="password" />
              <Button type="submit">Login</Button>
            </CardContent>
          </Card>
        </Form>
      );
    }
    ```
  </Tab>
</Tabs>

<Callout type="info">
  See all field components on the [Fields page](./fields).
</Callout>
