

The Form Builder provides a typed toolkit for building data-entry experiences ranging from single-record forms to high-volume CSV imports. It layers reusable primitives on top of [TanStack Form](https://tanstack.com/form/latest), [Zod](https://zod.dev/), and [Papa Parse](https://www.papaparse.com/).

## Getting Started [#getting-started]

```tsx
import { FormBuilder } from "@tilt-legal/cubitt-components/composites";
```

<Cards>
  <Card title="FormBuilder.Single" description="Schema-driven single-record forms with multi-step wizard support." href="./single" />

  <Card title="FormBuilder.Bulk" description="Bulk ingestion with CSV/file drop, header mapping, and grid editing." href="./bulk" />

  <Card title="Form Definitions" description="Declarative tree of steps, groups, and fields shared across both builders." href="./form-defs" />

  <Card title="Validation" description="Shared validation patterns for client and server-side error handling." href="./validation" />
</Cards>

## Core Concepts [#core-concepts]

### Typed Schemas [#typed-schemas]

Every flow starts with a Zod schema. The schema is passed to TanStack Form for client-side validation and can be reused server-side for parity.

```tsx
import {
  z } from "zod";

export const memberSchema = z.object({
  firstName: z.string().min(1,
  "Required"),
  lastName: z.string().min(1,
  "Required"),
  email: z.string().email("Invalid email"),
  });

export type Member = z.infer<typeof memberSchema>;
```

### Form Definitions [#form-definitions]

`formDefs` is a declarative array describing your form's structure—steps,
groups,
and fields. Both `FormBuilder.Single` and `FormBuilder.Bulk` consume the same definitions,
so you maintain one source of truth.

```tsx
import type { FormDefs } from "@tilt-legal/cubitt-components/composites";

const formDefs = [
  {
    kind: "group",
    title: "Contact",
    children: [
      { kind: "field", name: "firstName", label: "First name", component: "text", size: "half" },
      { kind: "field", name: "lastName", label: "Last name", component: "text", size: "half" },
      { kind: "field", name: "email", label: "Email", component: "email", size: "full" },
    ],
  },
] as const satisfies FormDefs<Member>;
```

### Submit Contracts [#submit-contracts]

* **Single forms**: `onSubmit(values)` receives the form data. Return a `ZodError` for server-side validation failures.
* **Bulk forms**: `onSubmit({ rows })` receives the array of rows. Return a `BulkSubmitFailure` to surface errors in the grid.

## Utilities [#utilities]

The form builder exports several utilities for advanced use cases:

```tsx
import {
  useBulkFormBuilder,
  injectFormError,
  shapeRows,
  mapZodError,
} from "@tilt-legal/cubitt-components/composites";
import {
  // Form creation hooks
  useFormBuilder,
  // Error injection
  injectZodErrors,
  // Data utilities
  flattenFormDefs,
  // Field rendering
  FieldRenderer,
} from "@tilt-legal/cubitt-components/composites";
```

| Utility              | Description                                                 |
| -------------------- | ----------------------------------------------------------- |
| `useFormBuilder`     | Low-level hook for creating single forms with TanStack Form |
| `useBulkFormBuilder` | Low-level hook for creating bulk forms with validation      |
| `injectZodErrors`    | Inject ZodError issues into a form's field errors           |
| `injectFormError`    | Inject form-level error messages                            |
| `flattenFormDefs`    | Flatten nested formDefs into a flat field array             |
| `shapeRows`          | Coerce raw CSV data using field metadata                    |
| `mapZodError`        | Map ZodError to field paths for display                     |
| `FieldRenderer`      | Render individual fields from formDefs                      |
