react-form-codegen
v0.2.1
Published
Generate type-safe React Hook Form components from TypeScript config - Zero boilerplate, full type safety, zero runtime overhead
Maintainers
Readme
react-form-codegen
Build forms like Lego. Designers style, backend defines submit, frontend authors build inputs. FormCreator wires it all together using React Hook Form.
💡 Concept
FormCreator is not just a form library. It's a team collaboration framework for building forms.
Config (TypeScript)
↓
Generator (builds field components, hooks, form)
↓
Individual Field Components + Main Form + useFormContext hooks
↓
Render in React AppWho does what:
- 🎨 Designers supply styling (Tailwind, Bootstrap, CSS Modules, SCSS)
- 🔐 Backend provides submit logic and validation rules
- ⚛️ Frontend devs create custom components (Dropzone, DatePicker, RichTextEditor)
- ⚙️ FormCreator generates:
- ✅ Fully typed hooks
- ✅ Individual field components
- ✅ Main form component
- ✅ Optional global state management
- ✅ Multi-step navigation hooks
🎯 Why FormCreator?
- Write forms in TypeScript, not JSX - Define once, generate clean React components
- Type-safe by default - Automatic interface extraction, full IntelliSense support
- Zero runtime overhead - Generates vanilla React Hook Form code
- Team-friendly architecture - Everyone works in their lane
- Copy-paste safe - Extract types, hooks, and components to separate files
- Accessible out of the box - ARIA attributes, semantic HTML, screen reader support
- CRUD-ready - Same form for create & update with
defaultValues - Multi-step wizards - Built-in support with progress tracking
- Framework agnostic - Works with any React setup (Next.js, Vite, CRA)
📦 Installation
npm install react-form-codegen react-hook-form🚀 Quick Start
1️⃣ Define Your Form
// forms/contact.form.ts
import { defineForm } from 'react-form-codegen';
export interface ContactFormValues {
name: string;
email: string;
message: string;
}
export default defineForm<ContactFormValues>()({
name: 'ContactForm',
mode: 'onBlur',
styling: {
type: 'tailwind',
classes: {
fieldWrapper: 'mb-4',
label: 'block text-sm font-medium text-gray-700 mb-2',
input: 'w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500',
error: 'text-red-600 text-sm mt-1',
},
},
inputs: {
name: {
component: 'input',
type: 'text',
label: 'Your Name',
placeholder: 'John Doe',
rules: {
required: 'Name is required',
minLength: { value: 2, message: 'At least 2 characters' },
},
},
email: {
component: 'input',
type: 'email',
label: 'Email Address',
placeholder: '[email protected]',
rules: {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address',
},
},
},
message: {
component: 'textarea',
label: 'Message',
placeholder: 'Your message here...',
rules: {
required: 'Message is required',
minLength: { value: 10, message: 'At least 10 characters' },
},
},
},
});2️⃣ Generate Component
npx create-form forms/contact.form.tsWhat happens?
Your config:
email: {
component: 'input',
type: 'email',
label: 'Email Address',
rules: { required: 'Email is required' }
}Becomes:
<ContactForm.Fields.email />
// OR use the typed hook:
const email = useContactFormField('email');
// email.value, email.error, email.setValue are all typed!CLI Options:
# Generate with automatic validation
npx create-form forms/contact.form.ts --test
# Watch mode - auto-regenerate on file changes
npx create-form forms/contact.form.ts --watch
# Watch mode with validation
npx create-form forms/contact.form.ts --watch --test
# Strict mode - treat warnings as errors
npx create-form forms/contact.form.ts --test --strict
# Custom output directory
npx create-form forms/contact.form.ts --output src/components/formsValidation Checks:
- ✅ No legacy React imports
- ✅ Required react-hook-form imports present
- ✅ Form component properly exported
- ✅ Type definitions exported
- ✅ No duplicate imports
- ✅ Valid TypeScript syntax
- ✅ ESLint disable comment present
- ✅ "DO NOT EDIT" warning banner present
3️⃣ Use in Your App
import { ContactForm } from './generated/ContactForm';
function App() {
const handleSubmit = async (data: ContactFormValues) => {
await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify(data),
});
};
return <ContactForm onSubmit={handleSubmit} />;
}What You Get:
The generator creates a complete, production-ready form with:
// ✅ Main Form Component
<ContactForm onSubmit={handleSubmit} defaultValues={initialData} />
// ✅ Individual Field Components (reusable!)
<ContactForm.Fields.name />
<ContactForm.Fields.email />
<ContactForm.Fields.message />
// ✅ Typed Context Hooks
const { watch, setValue } = useContactFormContext();
const email = watch('email'); // Fully typed!
setValue('email', '[email protected]'); // ✅ Works
setValue('email', 123); // ❌ TypeScript error!
setValue('nonexistent', 'value'); // ❌ TypeScript error!
// ✅ Field-specific Hooks
const { value, error, setValue } = useContactFormField('email');
// ✅ Submit Status Hook
const { isSubmitting, isValid } = useContactFormSubmitStatus();
// ✅ Optional Global State Hook
const { formData, setField, reset } = useContactFormData();What you save:
- ⏱️ 200-300 lines of boilerplate per form
- 🐛 Manual
register()calls and error handling - 🔄 Refactoring when adding/removing fields
- 🧪 Writing tests for form state logic
- 📝 TypeScript interfaces and type guards
👥 How It Works in a Team
Designer provides styling via config, no code changes needed:
styling: {
type: 'tailwind',
classes: {
input: 'w-full px-4 py-2 border rounded-lg focus:ring-2',
error: 'text-red-600 text-sm mt-1',
}
}Backend defines validation & submit logic, pure business rules:
rules: {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email'
}
}Frontend builds custom components without handling form state:
avatar: {
component: 'custom',
componentImport: './components/ImageUploader',
label: 'Profile Picture',
}🔥 Multi-Step Forms
export default defineForm<OnboardingFormValues>()({
name: 'OnboardingForm',
multiStep: true,
inputs: [
{
title: 'Personal Info',
description: 'Tell us about yourself',
fields: {
fullName: { component: 'input', type: 'text', label: 'Full Name' },
email: { component: 'input', type: 'email', label: 'Email' },
}
},
{
title: 'Address',
fields: {
street: { component: 'input', type: 'text', label: 'Street' },
city: { component: 'input', type: 'text', label: 'City' },
}
}
]
});Generates navigation hooks and step management:
const {
currentStep,
nextStep,
prevStep,
goToStep,
isFirstStep,
isLastStep
} = useOnboardingFormSteps();
<button onClick={prevStep} disabled={isFirstStep}>Back</button>
<button onClick={nextStep} disabled={isLastStep}>Next</button>🎨 Styling
Tailwind CSS
styling: {
type: 'tailwind',
classes: {
formWrapper: 'space-y-6 max-w-2xl mx-auto',
label: 'block text-sm font-semibold text-gray-700 mb-2',
input: 'w-full px-4 py-2 border rounded-lg focus:ring-2',
error: 'text-red-600 text-sm mt-1',
}
}Bootstrap
styling: { type: 'bootstrap', version: 5 }
// Auto-applies: form-control, form-label, invalid-feedback, etc.CSS/SCSS Modules
styling: {
type: 'css-modules',
modulePath: './ContactForm.module.css'
}⚡ Key Features
Copy-Paste Safe - Extract generated types and hooks to separate files:
/* SECTION 1: TYPE DEFINITIONS [COPY-PASTE SAFE] */
export interface ContactFormValues { ... }
/* SECTION 2: HOOKS [COPY-PASTE SAFE] */
export function useContactFormContext() { ... }Custom Components - Integrate any React component:
avatar: {
component: 'custom',
componentImport: './components/Dropzone',
controller: true,
}CRUD Ready - Same form for create and update:
<UserForm onSubmit={createUser} />
<UserForm defaultValues={existingUser} onSubmit={updateUser} />Full Validation - React Hook Form validation support:
rules: {
required: 'Email is required',
pattern: { value: /regex/, message: 'Invalid' },
validate: {
custom: (value) => value.includes('@') || 'Must contain @',
},
}📚 Examples
Check out examples/ for complete working examples:
- contact.form.ts - Simple contact form with Bootstrap styling
- login.form.ts - Login form with validation
- registration.form.ts - User registration with password confirmation
- multistep.form.ts - Multi-step wizard with progress tracking
- survey.form.ts - Survey form with various field types
- fileupload.form.ts - Form with custom file upload component
🚀 Use Cases
Onboarding flows - Multi-step registration and profile setup
Admin panels - CRUD interfaces with consistent patterns
Surveys - Dynamic forms with complex validation
Checkouts - Step-by-step purchasing flows
Battle-tested:
❯ src/__tests__/cli-index.test.ts 0/16
✓ src/__tests__/cli-index.test.ts (16 tests) 33ms
✓ src/__tests__/configValidator.test.ts (9 tests) 17ms
✓ src/__tests__/generatedCodeValidator.test.ts (12 tests) 20ms
✓ src/__tests__/fieldHelpers.test.ts (22 tests) 18ms
✓ src/__tests__/hooks.test.ts (16 tests) 15ms
✓ src/__tests__/styling.test.ts (20 tests) 13ms
✓ src/__tests__/generator.test.ts (11 tests) 8ms
✓ src/__tests__/configLoader.test.ts (16 tests) 117ms
Test Files 8 passed (8)
Tests 122 passed (122)
Start at 20:34:08
Duration 606ms (transform 1.29s, setup 0ms, import 1.70s, tests 242ms, environment 2ms) 🤔 FAQ
How do I add async options from an API?
const [countries, setCountries] = useState([]);
useEffect(() => {
fetch('/api/countries').then(res => res.json()).then(setCountries);
}, []);
<UserForm onSubmit={handleSubmit}>
<UserForm.Fields.country options={countries} />
</UserForm>Can I customize the generated code?
Yes. Look for [COPY-PASTE SAFE] sections. Extract them to your own files:
// types/user-form.ts
export interface UserFormValues { ... }Does it work with Next.js Server Components?
Generated forms are client components (they use hooks). Add 'use client' directive or use within client components.
Can I mix with existing React Hook Form code?
Yes. Generated components use standard RHF patterns and can be mixed with manual implementations.
🛠️ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
# Clone the repo
git clone https://github.com/Wajkie/formcreator.git
cd formcreator
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
# Run with coverage
npm test -- --coverage
# Development mode (watch for changes)
npm run devDevelopment workflow:
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Make your changes and add tests
- Run
npm run check-all(type-check, lint, and tests) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
📄 License
MIT © FormCreator Contributors
🔗 Links
- Examples - Working examples
- GitHub Repository - Source code
- Issue Tracker - Report bugs
- Discussions - Ask questions
🌟 Show Your Support
If this project helped you, please consider giving it a ⭐️ on GitHub!
Built with ❤️ using React Hook Form
