Generic Form
A schema-driven form system built on React Hook Form and Zod.
Requirement
This system requires
react-hook-form, zod, and @hookform/resolvers.Quickstart
- Ensure the registry is set up in Installation.
- Run the registry command:
npx shadcn@latest add https://gen-ui-eta-two.vercel.app/r/generic-formFeatures
- Type-Safe: Full TypeScript inference via Zod schemas.
- Layout Management: Grid layouts, sections, and responsive column spans.
- Conditional Logic: Hide/disable fields based on other values.
- Field Registry: Add custom field renderers globally.
- Optimized Renders: Field-level re-renders using
useWatch.
Basic Usage
import { GenericForm } from "@/components/systems/generic-form/generic-form"
import { z } from "zod"
const schema = z.object({
username: z.string().min(3),
email: z.string().email(),
role: z.enum(["admin", "user"]),
})
export function MyForm() {
return (
<GenericForm
schema={schema}
onSubmit={(data) => console.log(data)}
fields={{
username: { type: "text", label: "Username" },
email: { type: "email", label: "Email" },
role: {
type: "select",
label: "Role",
options: [
{ label: "Admin", value: "admin" },
{ label: "User", value: "user" },
],
},
}}
/>
)
}Layout And Sections
Use layout to control columns, spacing, and optional sections.
<GenericForm
schema={schema}
onSubmit={onSubmit}
layout={{
columns: { base: 1, md: 2 },
gap: "lg",
sections: [
{
id: "profile",
title: "Profile",
description: "Public info",
fields: ["username", "email"],
},
{
id: "permissions",
title: "Permissions",
fields: ["role"],
},
],
}}
fields={{
username: { type: "text", label: "Username" },
email: { type: "email", label: "Email" },
role: { type: "select", label: "Role", options: roleOptions },
}}
/>Conditional Logic
Use hidden, disabledWhen, and dependsOn to respond to other fields.
fields={{
role: {
type: "select",
label: "Role",
options: roleOptions,
},
adminCode: {
type: "text",
label: "Admin Code",
dependsOn: "role",
hidden: (values) => values.role !== "admin",
disabledWhen: (values) => values.role !== "admin",
},
}}Custom Fields
Use type: "custom" to render anything while still receiving form state.
fields={{
headline: {
type: "custom",
label: "Headline",
render: ({ value, onChange }) => (
<input
value={(value as string) ?? ""}
onChange={(e) => onChange(e.target.value)}
/>
),
},
}}Actions
Provide actions to add additional buttons or override the default submit/reset behavior.
<GenericForm
schema={schema}
onSubmit={onSubmit}
actions={[
{ id: "save", label: "Save", type: "submit", intent: "primary" },
{ id: "reset", label: "Reset", type: "reset" },
{ id: "preview", label: "Preview", type: "button", onClick: openPreview },
]}
fields={fields}
/>API Reference
| Prop | Type | Notes |
|---|---|---|
| schema | Zod object | Validation + inference |
| fields | Field config map | Controls inputs and labels |
| onSubmit | (values) => void | Submit handler |
| layout | FormLayout | Columns, gaps, sections |
| actions | FormAction[] | Extra buttons |
| showSubmit / showReset | boolean | Toggle default actions |
| renderField | (props) => ReactNode | Override rendering |