

<Tabs items="[&#x22;Preview&#x22;, &#x22;Code&#x22;]">
  <Tab value="Preview">
    <Preview name="SingleMultiStepWizardExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    "use client";

    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import { z } from "zod";
    import { FormBuilder } from "@tilt-legal/cubitt-components/composites";

    const schema = z.object({
      client: z.object({
        name: z.string().min(1, "Add a client name"),
        email: z.string().email("Enter a valid email"),
      }),
      engagement: z.object({
        type: z.enum(["standard", "custom"]),
        startDate: z.date(),
        notes: z.string().optional(),
      }),
      confirm: z.boolean().refine(Boolean, "Please confirm before submitting"),
    });

    type WizardValues = z.infer<typeof schema>;

    const formDefs = [
      {
        kind: "step",
        id: "client",
        title: "Client details",
        nextLabel: "Next: Engagement",
        children: [
          {
            kind: "group",
            children: [
              {
                kind: "field",
                name: "client.name",
                label: "Client name",
                component: "text",
                size: "half",
              },
              {
                kind: "field",
                name: "client.email",
                label: "Client email",
                component: "email",
                size: "half",
              },
            ],
          },
        ],
      },
      {
        kind: "step",
        id: "engagement",
        title: "Engagement setup",
        nextLabel: "Review",
        children: [
          {
            kind: "group",
            title: "Engagement details",
            children: [
              {
                kind: "field",
                name: "engagement.type",
                label: "Type",
                component: "radio",
                options: [
                  { value: "standard", label: "Standard" },
                  { value: "custom", label: "Custom" },
                ],
              },
              {
                kind: "field",
                name: "engagement.startDate",
                label: "Start date",
                component: "date",
                size: "half",
              },
              {
                kind: "field",
                name: "engagement.notes",
                label: "Customization notes",
                component: "textarea",
                size: "full",
                showIf: (v: WizardValues) => v.engagement?.type === "custom",
              },
            ],
          },
        ],
      },
      {
        kind: "step",
        title: "Review & confirm",
        previousLabel: "Back",
        nextLabel: "Submit",
        children: [
          {
            kind: "group",
            children: [
              {
                kind: "field",
                name: "confirm",
                label: "I confirm these details are correct",
                component: "checkbox",
              },
            ],
          },
        ],
      },
    ] as const satisfies FormDefs<WizardValues>;

    export default function WizardForm() {
      const stepper = FormBuilder.Single.useStepper<WizardValues>(() => null);

      return (
        <div>
          <FormBuilder.Single
            schema={schema}
            formDefs={formDefs}
            defaultValues={{
              client: { name: "", email: "" },
              engagement: { type: "standard", startDate: new Date(), notes: "" },
              confirm: false,
            }}
            stepper={stepper.render}
            validateOnBack
            onSubmit={async (values) => console.log(values)}
          />

          <div className="mt-6 flex justify-end gap-2">
            <Button
              variant="secondary"
              disabled={stepper.state.isFirst}
              onClick={() => stepper.actions.previous()}
            >
              Back
            </Button>
            <Button
              onClick={() =>
                stepper.state.isLast
                  ? stepper.actions.submit()
                  : stepper.actions.next()
              }
            >
              {stepper.state.isLast ? "Submit" : "Next"}
            </Button>
          </div>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

## How It Works [#how-it-works]

1. **Define step nodes** – Use `kind: "step"` with `id`, `title`, and `children`
2. **Get stepper handle** – Call `FormBuilder.Single.useStepper(() => null)` to hide default UI
3. **Pass stepper.render** – Connects the stepper to the form
4. **Build custom controls** – Use `stepper.state` and `stepper.actions`

<Callout type="info">
  See [Step Nodes](/composites/form-builder/form-defs#step-nodes) for step
  properties and [Stepper Hook](/composites/form-builder/single#stepper-hook)
  for the full state/actions API.
</Callout>
