一覧へ戻る
F-47/formcn(新しいタブで開きます)
主要言語: JavaScript
Schema-driven form generator using React Hook Form, Zod, and shadcn/ui
- star 数
- 11
- watcher 数
- 0
- fork 数
- 0
- issue 数
- 0
- 最終更新
- 2026/01/08
主要言語: JavaScript
Schema-driven form generator using React Hook Form, Zod, and shadcn/ui
Schema-driven React form generator built on top of React Hook Form, Zod, and shadcn/ui.
formcn generates fully typed, validated React form components from an interactive CLI workflow. It is designed to reduce boilerplate while staying aligned with shadcn/ui conventions.
Before using formcn, ensure you have:
Initialize shadcn/ui if needed:
npx shadcn@latest init
Install globally using npm:
npm install -g formcn
Or using yarn:
yarn global add formcn
Or run directly with npx:
npx formcn
Run the CLI:
formcn
The interactive workflow will guide you through:
$ 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
src/components/forms/{formName}/
├── schema.ts
└── form.tsxsrc/components/forms/user-registration/ ├── personal-info-schema.ts ├── personalInfoStep.tsx ├── account-details-schema.ts ├── accountDetailsStep.tsx ├── contact-info-schema.ts ├── contactInfoStep.tsx └── UserRegistrationForm.tsx
default — minimal layout with subtle borders and spacingminimalsidebarSteppersoftTypestepperTopimport { 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<typeof schema>;
"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<SchemaFormValues>({ resolver: zodResolver(schema), }); function onSubmit(values: SchemaFormValues) { console.log(values); } return ( <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 max-w-3xl mx-auto border rounded-lg p-6" > <FieldGroup /> <div className="flex justify-end"> <Button type="submit" disabled={!form.formState.isValid}> Submit </Button> </div> </form> ); }
Ensure your global npm bin directory is in your PATH:
npm config get prefix
npx shadcn@latest init
npm install react-hook-form @hookform/resolvers zod
Contributions are welcome.
MIT License
Fares Galal