Generic Form

A schema-driven form system built on React Hook Form and Zod.

Quickstart

  1. Ensure the registry is set up in Installation.
  2. Run the registry command:
npx shadcn@latest add https://gen-ui-eta-two.vercel.app/r/generic-form

Features

  • 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

PropTypeNotes
schemaZod objectValidation + inference
fieldsField config mapControls inputs and labels
onSubmit(values) => voidSubmit handler
layoutFormLayoutColumns, gaps, sections
actionsFormAction[]Extra buttons
showSubmit / showResetbooleanToggle default actions
renderField(props) => ReactNodeOverride rendering
← Previous: InstallationNext: Generic Table