Schema-driven React form generator using React Hook Form, Zod, and shadcn/ui
npm install formcnbash
npx shadcn@latest init
`
---
Installation
Install globally using npm:
`bash
npm install -g formcn
`
Or using yarn:
`bash
yarn global add formcn
`
Or run directly with npx:
`bash
npx formcn
`
---
Usage
Run the CLI:
`bash
formcn
`
The interactive workflow will guide you through:
1. Form name
2. Form type (single-step or multi-step)
3. Template selection
4. Field definitions and validation rules
5. Style preset selection
$3
`bash
$ formcn
formcn
✔ react-hook-form detected
✔ @hookform/resolvers detected
✔ zod detected
✔ shadcn/ui components detected
Form name: register
Form type: single step
Template: registration
Preset: default
✔ Form generated at src/components/forms/register
`
---
Generated Output
$3
`txt
src/components/forms/{formName}/
├── schema.ts
└── form.tsx
`
$3
`txt
src/components/forms/user-registration/
├── personal-info-schema.ts
├── personalInfoStep.tsx
├── account-details-schema.ts
├── accountDetailsStep.tsx
├── contact-info-schema.ts
├── contactInfoStep.tsx
└── UserRegistrationForm.tsx
`
---
Supported Field Types
- Text
- Email
- Password (with optional confirmation)
- Number
- URL
- Textarea
- Select
- Checkbox
- Radio
- Date
---
Templates
$3
- Registration
- Login
- Contact
$3
- Registration (personal info, account details, contact info)
---
Style Presets
$3
- default — minimal layout with subtle borders and spacing
$3
- minimal
- sidebarStepper
- softType
- stepperTop
---
Example Output
$3
`ts
import { z } from "zod";
export const schema = z
.object({
first_name: z.string().min(1, "First Name is required"),
last_name: z.string().min(1, "Last Name is required"),
email: z.string().email("Invalid email"),
password: z.string().min(8, "Password must be at least 8 characters"),
passwordConfirmation: z.string(),
age: z.coerce.number().min(18, "Age must be at least 18"),
website: z.string().url("Invalid URL").optional().or(z.literal("")),
bio: z.string().optional(),
country: z.union([z.literal("us"), z.literal("ca"), z.literal("uk")], {
error: "Please select a country",
}),
newsletter: z.boolean().optional(),
})
.refine((data) => data.password === data.passwordConfirmation, {
message: "Passwords do not match",
path: ["passwordConfirmation"],
});
export type SchemaFormValues = z.infer;
`
$3
`tsx
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import { FieldGroup } from "@/components/ui/field";
import { schema, type SchemaFormValues } from "./schema";
export default function RegisterForm() {
const form = useForm({
resolver: zodResolver(schema),
});
function onSubmit(values: SchemaFormValues) {
console.log(values);
}
return (
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 max-w-3xl mx-auto border rounded-lg p-6"
>
);
}
`
---
Troubleshooting
$3
Ensure your global npm bin directory is in your PATH:
`bash
npm config get prefix
`
$3
`bash
npx shadcn@latest init
`
$3
`bash
npm install react-hook-form @hookform/resolvers zod
``