

`FormBuilder.Single` renders one form from a Zod schema and `formDefs`. It owns the Cubitt field UI, form wiring, conditional visibility, validation display, and optional stepper controls.

<Preview name="SingleMinimalSignInExample" />

## Usage [#usage]

```tsx
import { z } from "zod";
import {
  FormBuilder,
  type FormDefs,
} from "@tilt-legal/cubitt-components/form-builder";
```

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

const formDefs = [
  {
    kind: "field",
    name: "email",
    label: "Email",
    component: "email",
    size: "full",
  },
  {
    kind: "field",
    name: "password",
    label: "Password",
    component: "text",
    size: "full",
  },
] as const satisfies FormDefs;
```

```tsx
<FormBuilder.Single
  defaultValues={{ email: "", password: "" }}
  formDefs={formDefs}
  onSubmit={async (values) => {
    await signIn(values);
  }}
  schema={schema}
  submitLabel="Sign in"
/>
```

## Examples [#examples]

### Minimal Sign-In [#minimal-sign-in]

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

  <Tab value="Code">
    ```tsx
    <FormBuilder.Single
      defaultValues={{ email: "", password: "", tags: [], phone: "" }}
      formDefs={signInDefs}
      onSubmit={handleSubmit}
      schema={signInSchema}
      submitLabel="Sign in"
    />
    ```
  </Tab>
</Tabs>

### Grouped Layout [#grouped-layout]

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

  <Tab value="Code">
    ```tsx
    <FormBuilder.Single
      defaultValues={{
        title: "",
        code: "",
        description: "",
        practiceArea: "",
        priority: "normal",
        confidential: false,
      }}
      formDefs={groupedDefs}
      onSubmit={handleSubmit}
      schema={groupedSchema}
      submitLabel="Save"
    />
    ```
  </Tab>
</Tabs>

### Conditional Logic [#conditional-logic]

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

  <Tab value="Code">
    ```tsx
    const formDefs = [
      {
        kind: "field",
        name: "email",
        component: "email",
        showIf: (values) => values.mode === "advanced",
        requiredIf: (values) => values.mode === "advanced",
      },
    ];
    ```
  </Tab>
</Tabs>

### Multi-Step Wizard [#multi-step-wizard]

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

  <Tab value="Code">
    ```tsx
    const stepper = FormBuilder.Single.useStepper(() => null);

    <FormBuilder.Single
      defaultValues={wizardDefaults}
      formDefs={wizardDefs}
      onSubmit={handleSubmit}
      schema={wizardSchema}
      stepper={stepper.render}
      validateOnBack
    />;
    ```
  </Tab>
</Tabs>

### Stepper Navigation [#stepper-navigation]

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

  <Tab value="Code">
    ```tsx
    const stepper = FormBuilder.Single.useStepper(
      (slot) => slot.defaultStepper
    );

    <Stepper
      onValueChange={(index) => stepper.actions.goTo(index)}
      value={stepper.state.index}
    >
      {steps.map((step, index) => (
        <StepperItem
          disabled={index > stepper.state.index}
          key={step.id}
          step={index}
        >
          <StepperTrigger>
            <StepperIndicator />
            <StepperHeading>
              <StepperTitle>{step.title}</StepperTitle>
            </StepperHeading>
          </StepperTrigger>
        </StepperItem>
      ))}
    </Stepper>;
    ```
  </Tab>
</Tabs>

### External Submit [#external-submit]

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

  <Tab value="Code">
    ```tsx
    const formId = "member-form";

    <FormBuilder.Single
      defaultValues={{ name: "" }}
      formDefs={formDefs}
      id={formId}
      onSubmit={handleSubmit}
      schema={schema}
    />;

    <Button form={formId} type="submit">
      Submit
    </Button>;
    ```
  </Tab>
</Tabs>

## API Reference [#api-reference]

| Prop             | Type                              | Default    | Description                                                      |
| ---------------- | --------------------------------- | ---------- | ---------------------------------------------------------------- |
| `schema`         | `z.ZodTypeAny`                    | -          | Zod schema used for validation and type inference                |
| `formDefs`       | `FormDefs<T>`                     | -          | Step, group, and field definitions                               |
| `onSubmit`       | `(values: T) => FormSubmitResult` | -          | Submit handler. Return a `ZodError` to display server validation |
| `defaultValues`  | `Partial<T>`                      | `{}`       | Initial TanStack Form values                                     |
| `defaultStep`    | `number \| string`                | -          | Initial step index or step id                                    |
| `validateOnBack` | `boolean`                         | `false`    | Validate the current step before moving backward                 |
| `title`          | `string`                          | -          | Optional heading rendered above the form                         |
| `description`    | `string`                          | -          | Optional description rendered under the heading                  |
| `disabled`       | `boolean`                         | `false`    | Disable fields, stepper actions, and the default submit button   |
| `id`             | `string`                          | -          | Form id for external submit buttons                              |
| `submitLabel`    | `string \| null`                  | `"Submit"` | Built-in submit button label. Pass `null` to hide                |
| `footer`         | `ReactNode`                       | -          | Content rendered after fields before submit controls             |
| `stepper`        | `false \| StepperRender<T>`       | -          | Hide, replace, or extend the default stepper controls            |
| `groupClassName` | `string`                          | -          | Layout class for group wrappers                                  |
| `itemClassName`  | `string`                          | -          | Layout class for field containers                                |
| `className`      | `string`                          | -          | Layout class for the form element                                |
| `submitRef`      | `Ref<() => void>`                 | -          | Imperative submit function                                       |
| `formRef`        | `Ref<AnyReactFormApi>`            | -          | Underlying TanStack Form instance                                |

## Stepper Hook [#stepper-hook]

`FormBuilder.Single.useStepper()` returns a handle for external navigation.

| Property           | Type                                             | Description                              |
| ------------------ | ------------------------------------------------ | ---------------------------------------- |
| `state.index`      | `number`                                         | Current visible step index               |
| `state.total`      | `number`                                         | Total visible steps                      |
| `state.isFirst`    | `boolean`                                        | Whether the first visible step is active |
| `state.isLast`     | `boolean`                                        | Whether the last visible step is active  |
| `state.disabled`   | `boolean`                                        | Mirrors the form disabled state          |
| `state.step`       | `FormStep \| null`                               | Active step definition                   |
| `actions.next`     | `() => Promise<boolean>`                         | Validate and advance                     |
| `actions.previous` | `() => Promise<boolean>`                         | Move backward                            |
| `actions.goTo`     | `(target: number \| string) => Promise<boolean>` | Move to a step index or id               |
| `actions.submit`   | `() => Promise<boolean>`                         | Validate current step and submit         |
| `actions.reset`    | `() => void`                                     | Reset to the first step                  |
| `render`           | `(slot) => ReactNode`                            | Pass to the `stepper` prop               |
