npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

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

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.

TypeScript License: MIT


💡 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 App

Who 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.ts

What 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/forms

Validation 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:


🚀 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 dev

Development workflow:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Make your changes and add tests
  4. Run npm run check-all (type-check, lint, and tests)
  5. Commit your changes (git commit -m 'Add some AmazingFeature')
  6. Push to the branch (git push origin feature/AmazingFeature)
  7. Open a Pull Request

📄 License

MIT © FormCreator Contributors


🔗 Links


🌟 Show Your Support

If this project helped you, please consider giving it a ⭐️ on GitHub!


Built with ❤️ using React Hook Form