

<Preview name="ValidationFormExample" />

## Zod Integration [#zod-integration]

Define a Zod schema and pass it to TanStack Form through the dynamic validation channel.

```tsx
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("Please enter a valid email"),
  age: z.number().min(18, "You must be at least 18 years old"),
  password: z.string().min(8, "Password must be at least 8 characters"),
});

const form = useForm({
  defaultValues: {
    email: "",
    age: 18,
    password: "",
  },
  validationLogic: revalidateLogic({
    mode: "submit",
    modeAfterSubmission: "change",
  }),
  validators: {
    onDynamic: schema,
  },
});
```

## Cross-Field Validation [#cross-field-validation]

Use Zod refinements for validation that depends on multiple fields.

```tsx
const schema = z
  .object({
    password: z.string().min(8),
    confirmPassword: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "Passwords don't match",
    path: ["confirmPassword"],
  });
```

## Async Validation [#async-validation]

Use async validators for checks such as username or email availability.

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

  <Tab value="Code">
    ```tsx
    const checkUsernameAvailability = async (username: string) => {
      await new Promise((resolve) => setTimeout(resolve, 500));
      return !["admin", "user", "test"].includes(username.toLowerCase());
    };

    const schema = z.object({
      username: z.string().min(3, "Username must be at least 3 characters"),
      email: z.email("Invalid email address"),
    });
    ```

    ```tsx
    export function AsyncValidationExample() {
      const form = useForm({
        defaultValues: {
          username: "",
          email: "",
        },
        validationLogic: revalidateLogic({
          mode: "submit",
          modeAfterSubmission: "change",
        }),
        validators: {
          onDynamic: schema,
          onDynamicAsyncDebounceMs: 500,
          onDynamicAsync: async ({ value }) => {
            if (value.username.length < 3) {
              return;
            }

            const available = await checkUsernameAvailability(value.username);
            if (!available) {
              return {
                fields: {
                  username: "Username is already taken",
                },
              };
            }
          },
        },
      });

      return (
        <Form form={form} className="max-w-sm">
          <InputField
            name="username"
            label="Username"
            placeholder="Choose a unique username"
          />
          <InputField
            name="email"
            label="Email"
            type="email"
            placeholder="your@email.com"
          />
        </Form>
      );
    }
    ```
  </Tab>
</Tabs>

## Error Handling [#error-handling]

Field components read TanStack field meta and render errors through the shared form message chrome.

```tsx
<InputField name="username" label="Username" />
```

For advanced field-level control, pass a `field` instance directly.

```tsx
<form.Field name="username">
  {(field) => <InputField field={field} label="Username" />}
</form.Field>
```

## Server-Side Errors [#server-side-errors]

Server validation errors can be mapped back onto field meta with the form helpers.

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

const form = useForm({
  onSubmit: async ({ value, formApi }) => {
    try {
      await createAccount(value);
    } catch (error) {
      if (error.status === 422) {
        for (const [fieldName, message] of Object.entries(error.errors)) {
          setFieldErrors(formApi, fieldName, [String(message)]);
        }
      }
    }
  },
});
```

## Helper Utilities [#helper-utilities]

```tsx
import {
  extractErrorMessages,
  hasErrors,
  setFieldErrors,
  setFieldMeta,
  setFieldValue,
  validateField,
  validateForm,
} from "@tilt-legal/cubitt-components/form";
```

| Utility                | Description                               |
| ---------------------- | ----------------------------------------- |
| `extractErrorMessages` | Converts TanStack field errors to strings |
| `hasErrors`            | Checks whether a field has visible errors |
| `setFieldErrors`       | Sets custom errors on a field             |
| `setFieldMeta`         | Updates field meta with an updater        |
| `setFieldValue`        | Sets a field value                        |
| `validateField`        | Runs validation for one field             |
| `validateForm`         | Runs validation for the full form         |

<Callout type="info">
  These utilities are primarily used internally by the FormBuilder components.
  For most use cases, the standard TanStack Form API is sufficient.
</Callout>
