BulkPreview
CSV ingestion with column mapping, inline editing, validation, and row submission.
FormBuilder.Bulk reuses the same schema and formDefs as FormBuilder.Single, then adds CSV import, header mapping, a grid editor, validation, template download, and invalid-row export.
Usage
import { z } from "zod";
import {
FormBuilder,
type FormDefs,
} from "@tilt-legal/cubitt-components/form-builder";const memberSchema = z.object({
firstName: z.string().min(1, "Required"),
email: z.string().email(),
role: z.enum(["admin", "member"]).default("member"),
active: z.boolean().default(true),
});
const memberDefs = [
{
kind: "field",
name: "firstName",
label: "First name",
component: "text",
size: "half",
},
{
kind: "field",
name: "email",
label: "Email",
component: "email",
size: "full",
},
{
kind: "field",
name: "role",
label: "Role",
component: "select",
options: [
{ value: "admin", label: "Admin" },
{ value: "member", label: "Member" },
],
size: "full",
},
{
kind: "field",
name: "active",
label: "Active",
component: "switch",
size: "full",
},
] as const satisfies FormDefs;<FormBuilder.Bulk
formDefs={memberDefs}
onSubmit={async ({ rows }) => {
await importMembers(rows);
}}
schema={memberSchema}
template={{ filename: "members.csv" }}
/>Examples
Basic Import
<FormBuilder.Bulk
formDefs={memberDefs}
onSubmit={handleImport}
schema={memberSchema}
template={{ filename: "members.csv" }}
/>CSV Flow
- Drop, choose, or paste CSV data.
- Headers are mapped to fields by name, label, and aliases.
- Raw CSV rows are shaped into schema values.
- Rows render in a table using Cubitt form fields.
- Zod validation populates inline row errors.
- Submit receives
{ rows }.
Mapping Aliases
<FormBuilder.Bulk
formDefs={memberDefs}
mapping={{
headerAliases: {
firstName: ["given_name", "first"],
active: ["enabled", "status"],
},
}}
onSubmit={handleImport}
schema={memberSchema}
/>Template Downloads
<FormBuilder.Bulk
formDefs={memberDefs}
onSubmit={handleImport}
schema={memberSchema}
template={{
filename: "members-template.csv",
exampleRow: {
firstName: "Jane",
email: "jane@example.com",
role: "member",
active: true,
},
}}
/>Controlled Rows
const [rows, setRows] = useState<Member[]>([]);
<FormBuilder.Bulk
formDefs={memberDefs}
onRowsChange={setRows}
onSubmit={handleImport}
rows={rows}
schema={memberSchema}
/>API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
schema | z.ZodTypeAny | - | Zod schema applied to each row |
formDefs | FormDefs<T> | - | Shared definitions for columns, labels, field components, and CSV templates |
onSubmit | (opts: { rows: T[] }) => BulkSubmitResult<T> | - | Submit handler. Return a BulkSubmitFailure to display row or form errors |
onSubmitInvalid | () => void | - | Called when the default toolbar submit button is pressed while rows are invalid |
parseOptions | { delimiter?, headerRow?, maxRows?, localeDecimal? } | - | CSV parser configuration |
mapping | { headerAliases?: Record<string, string[]> } | - | Header aliases for auto-mapping |
template | { filename?, exampleRow? } | - | Template download filename and optional example row |
accept | string | ".csv" | File picker accept string |
validateOnEdit | boolean | true | Revalidate rows while editing |
rows | T[] | - | Controlled rows |
onRowsChange | (rows: T[]) => void | - | Row change callback |
disabled | boolean | false | Disable ingest, editing, mapping, and actions |
className | string | - | Layout class for the root form |
id | string | - | Form id for external submit buttons |
submitRef | Ref<() => void> | - | Imperative submit function |
toolbar | false | RenderToolbar | - | Hide, replace, or extend the toolbar |
enableExportInvalidCsv | boolean | true | Show the invalid-row export action |
Toolbar Hook
FormBuilder.Bulk.useToolbar() returns a handle for external toolbar composition.
| Property | Type | Description |
|---|---|---|
actions.uploadFile | () => void | Open the file picker |
actions.reviewMapping | () => void | Open the mapping dialog |
actions.downloadTemplate | () => void | Download the CSV template |
actions.validateAll | () => void | Validate all rows |
actions.submit | () => void | Submit the form |
actions.exportInvalidCsv | () => void | Export invalid rows |
render | (slot) => ReactNode | Pass to the toolbar prop |
Error Return
type BulkSubmitFailure = {
error?: ZodError;
formErrors?: string[];
rows?: Array<{
rowIndex: number;
fieldErrors: Record<string, string[]>;
rowErrors: string[];
}>;
};