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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@connect-soft/form-generator

v1.1.0-alpha5

Published

Headless, type-safe form generator with react-hook-form and Zod validation

Downloads

357

Readme

@connect-soft/form-generator

Headless, type-safe form generator with react-hook-form and Zod validation

Version License


Features

  • Headless: Bring your own UI components (Radix, MUI, Chakra, or plain HTML)
  • Type-Safe: Full TypeScript inference for form values and field types
  • Field Type Checking: Compile-time validation of field.type with autocomplete
  • Extensible Types: Add custom field types via module augmentation
  • Imperative API: Control form via ref (setValues, reset, submit, etc.)
  • Flexible: Register custom field components with a simple API
  • Validation: Built-in Zod validation support
  • Layouts: Support for sections and multi-column layouts
  • Lightweight: No UI dependencies, minimal footprint
  • HTML Fallbacks: Works out of the box with native HTML inputs

Installation

npm install @connect-soft/form-generator

Peer Dependencies

  • react ^19.0.0
  • react-dom ^19.0.0
  • zod ^4.0.0

Quick Start

The library works immediately with HTML fallback components:

import { FormGenerator } from '@connect-soft/form-generator';

const fields = [
  { type: 'text', name: 'email', label: 'Email', required: true },
  { type: 'number', name: 'age', label: 'Age', min: 18, max: 120 },
  { type: 'select', name: 'country', label: 'Country', options: [
    { label: 'United States', value: 'us' },
    { label: 'Germany', value: 'de' },
  ]},
  { type: 'checkbox', name: 'subscribe', label: 'Subscribe to newsletter' },
] as const;

function MyForm() {
  return (
    <FormGenerator
      fields={fields}
      onSubmit={(values) => {
        console.log(values); // Fully typed!
      }}
    />
  );
}

Registering Custom Components

Register your own UI components to replace the HTML fallbacks:

import { registerFields, registerFormComponents } from '@connect-soft/form-generator';
import { Input } from './ui/input';
import { Label } from './ui/label';
import { Checkbox } from './ui/checkbox';
import { Button } from './ui/button';

// Register field components
registerFields({
  text: ({ field, formField }) => (
    <Input
      {...formField}
      type={field.fieldType || 'text'}
      placeholder={field.placeholder}
      disabled={field.disabled}
    />
  ),
  number: ({ field, formField }) => (
    <Input
      {...formField}
      type="number"
      min={field.min}
      max={field.max}
    />
  ),
  checkbox: {
    component: ({ field, formField }) => (
      <Checkbox
        checked={formField.value}
        onCheckedChange={formField.onChange}
        disabled={field.disabled}
      />
    ),
    options: {
      className: 'flex items-center gap-2',
    }
  },
});

// Register form wrapper components
registerFormComponents({
  FormItem: ({ children, className }) => <div className={className}>{children}</div>,
  FormLabel: Label,
  FormMessage: ({ children }) => <span className="text-red-500 text-sm">{children}</span>,
  SubmitButton: Button,
});

Field Types

Built-in HTML fallback types:

| Type | Description | Value Type | |------|-------------|------------| | text | Text input | string | | email | Email input | string | | password | Password input | string | | number | Number input with min/max | number | | textarea | Multi-line text | string | | checkbox | Checkbox | boolean | | select | Dropdown select | string | | radio | Radio button group | string | | date | Date input | Date | | time | Time input | string | | file | File input | File | | hidden | Hidden input | string |

Adding Custom Field Types (TypeScript)

Extend the FieldTypeRegistry interface to add type checking for custom fields:

// types/form-generator.d.ts
import { CreateFieldType } from '@connect-soft/form-generator';

declare module '@connect-soft/form-generator' {
  interface FieldTypeRegistry {
    // Add your custom field types
    'color-picker': CreateFieldType<'color-picker', string, {
      swatches?: string[];
      showAlpha?: boolean;
    }>;
    'rich-text': CreateFieldType<'rich-text', string, {
      toolbar?: ('bold' | 'italic' | 'link')[];
      maxLength?: number;
    }>;
  }
}

Now TypeScript will recognize your custom field types:

const fields = [
  { type: 'color-picker', name: 'theme', swatches: ['#fff', '#000'] }, // ✅ Valid
  { type: 'unknown-type', name: 'test' }, // ❌ Type error
] as const;

Don't forget to register the component for your custom field:

import { registerField } from '@connect-soft/form-generator';
import { ColorPicker } from './components/ColorPicker';

registerField('color-picker', ({ field, formField }) => (
  <ColorPicker
    value={formField.value}
    onChange={formField.onChange}
    swatches={field.swatches}
    showAlpha={field.showAlpha}
  />
));

Custom Validation

Use Zod for field-level or form-level validation:

import { z } from 'zod';

// Field-level validation
const fields = [
  {
    type: 'text',
    name: 'username',
    label: 'Username',
    validation: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
  },
  {
    type: 'text',
    name: 'email',
    label: 'Email',
    validation: z.string().email(),
  },
] as const;

// Or use a full schema for type inference
const schema = z.object({
  username: z.string().min(3),
  email: z.string().email(),
});

<FormGenerator
  fields={fields}
  schema={schema}
  onSubmit={(values) => {
    // values is inferred from schema
  }}
/>

Layouts

Organize fields with sections and columns:

const fields = [
  {
    type: 'section',
    title: 'Personal Information',
    children: [
      { type: 'text', name: 'firstName', label: 'First Name' },
      { type: 'text', name: 'lastName', label: 'Last Name' },
    ],
  },
  {
    type: 'columns',
    columns: [
      {
        width: '1',
        children: [
          { type: 'text', name: 'city', label: 'City' },
        ],
      },
      {
        width: '1',
        children: [
          { type: 'text', name: 'zip', label: 'ZIP Code' },
        ],
      },
    ],
  },
];

Custom Layouts

For full control over form layout, pass a render function as children:

<FormGenerator
  fields={[
    { type: 'text', name: 'email', label: 'Email' },
    { type: 'password', name: 'password', label: 'Password' },
  ] as const}
  title="Login"
  onSubmit={handleSubmit}
>
  {({ fields, buttons, title }) => (
    <div className="login-form">
      <h1>{title}</h1>
      <div className="field-row">{fields.email}</div>
      <div className="field-row">{fields.password}</div>
      <div className="actions">{buttons.submit}</div>
    </div>
  )}
</FormGenerator>

Render Props API

The render function receives:

| Property | Type | Description | |----------|------|-------------| | fields | TemplateFields | Pre-rendered fields (see below) | | layouts | TemplateLayouts | Pre-rendered named layouts | | buttons | { submit, reset? } | Pre-rendered buttons | | title | string | Form title prop | | description | string | Form description prop | | form | UseFormReturn | react-hook-form instance | | isSubmitting | boolean | Form submission state | | isValid | boolean | Form validity state | | isDirty | boolean | Form dirty state | | renderField | function | Manual field renderer | | renderLayout | function | Manual layout renderer |

Fields Object

Access fields by name or use helper methods:

{({ fields }) => (
  <div>
    {/* Access individual fields */}
    {fields.email}
    {fields.password}

    {/* Render all fields */}
    {fields.all}

    {/* Render only fields not yet accessed */}
    {fields.remaining}

    {/* Check if field exists */}
    {fields.has('email') && fields.email}

    {/* Get all field names */}
    {fields.names.map(name => <div key={name}>{fields[name]}</div>)}

    {/* Render specific fields */}
    {fields.render('email', 'password')}
  </div>
)}

Mixed Layout Example

Highlight specific fields while rendering the rest normally:

<FormGenerator fields={fieldDefinitions} onSubmit={handleSubmit}>
  {({ fields, buttons }) => (
    <div>
      <div className="highlighted">{fields.email}</div>
      <div className="other-fields">{fields.remaining}</div>
      {buttons.submit}
    </div>
  )}
</FormGenerator>

Form State Access

Use form state for conditional rendering:

<FormGenerator fields={fieldDefinitions} onSubmit={handleSubmit}>
  {({ fields, buttons, isSubmitting, isValid, isDirty }) => (
    <div>
      {fields.all}
      <button type="submit" disabled={isSubmitting || !isValid}>
        {isSubmitting ? 'Saving...' : 'Submit'}
      </button>
      {isDirty && <span>You have unsaved changes</span>}
    </div>
  )}
</FormGenerator>

TypeScript Type Inference

Get full type inference from field definitions:

const fields = [
  { type: 'text', name: 'email', required: true },
  { type: 'number', name: 'age', required: true },
  { type: 'checkbox', name: 'terms' },
] as const;

<FormGenerator
  fields={fields}
  onSubmit={(values) => {
    values.email;  // string
    values.age;    // number
    values.terms;  // boolean | undefined
  }}
/>

Or provide an explicit Zod schema:

const schema = z.object({
  email: z.string().email(),
  age: z.number().min(18),
  terms: z.boolean(),
});

<FormGenerator
  fields={fields}
  schema={schema}
  onSubmit={(values) => {
    // values: { email: string; age: number; terms: boolean }
  }}
/>

Imperative API (Ref)

Access form methods programmatically using a ref:

import { useRef } from 'react';
import { FormGenerator, FormGeneratorRef } from '@connect-soft/form-generator';

function MyForm() {
  const formRef = useRef<FormGeneratorRef>(null);

  const handleExternalSubmit = async () => {
    await formRef.current?.submit();
  };

  const handleReset = () => {
    formRef.current?.reset();
  };

  const handleSetValues = () => {
    formRef.current?.setValues({
      email: '[email protected]',
      age: 25,
    });
  };

  return (
    <>
      <FormGenerator
        ref={formRef}
        fields={fields}
        onSubmit={(values) => console.log(values)}
      />
      <button type="button" onClick={handleExternalSubmit}>Submit Externally</button>
      <button type="button" onClick={handleReset}>Reset Form</button>
      <button type="button" onClick={handleSetValues}>Set Values</button>
    </>
  );
}

Available Ref Methods

| Method | Description | |--------|-------------| | setValues(values) | Set form values (partial update) | | getValues() | Get current form values | | reset(values?) | Reset to default or provided values | | submit() | Programmatically submit the form | | clearErrors() | Clear all validation errors | | setError(name, error) | Set error for a specific field | | isValid() | Check if form passes validation | | isDirty() | Check if form has unsaved changes | | form | Access underlying react-hook-form instance |


API Reference

FormGenerator Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | fields | FormItem[] | required | Array of field definitions | | onSubmit | (values) => void \| Promise<void> | required | Form submission handler | | schema | ZodType | - | Optional Zod schema for validation | | defaultValues | object | {} | Initial form values | | className | string | - | CSS class for form element | | submitText | string | 'Submit' | Submit button text | | disabled | boolean | false | Disable entire form | | mode | 'onChange' \| 'onBlur' \| 'onSubmit' \| 'onTouched' \| 'all' | 'onChange' | Validation trigger mode | | children | TemplateRenderFn | - | Render function for custom layout | | title | string | - | Form title (available in render props) | | description | string | - | Form description (available in render props) | | showReset | boolean | false | Include reset button in buttons.reset | | resetText | string | 'Reset' | Reset button text |

Field Base Properties

| Property | Type | Description | |----------|------|-------------| | type | string | Field type (text, number, select, etc.) | | name | string | Field name (must be unique) | | label | string | Field label | | description | string | Helper text below field | | required | boolean | Mark field as required | | disabled | boolean | Disable field | | hidden | boolean | Hide field | | defaultValue | any | Default field value | | validation | ZodType | Zod validation schema | | className | string | CSS class for field wrapper |


Links


License

ISC © Connect Soft