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

@classytic/formkit

v1.0.3

Published

Headless, type-safe form generation engine for React. Schema-driven with full TypeScript support.

Readme

@classytic/formkit

Headless, type-safe form generation engine for React 18/19. Schema-driven with full TypeScript support.

npm License: MIT TypeScript

Features

  • 🎯 Headless - Bring your own UI components (Shadcn, MUI, Chakra, etc.)
  • 📝 Schema-driven - Define forms with JSON/TypeScript schemas
  • 🔒 Type-safe - Full TypeScript support with generics
  • React Hook Form - Built on top of the best form library
  • 🎨 Variants - Support for multiple component variants
  • 🔀 Conditional fields - Show/hide fields based on form values
  • 📱 Responsive layouts - Multi-column grid layouts
  • 🪶 Lightweight - ~6KB minified, tree-shakeable

Installation

npm install @classytic/formkit react-hook-form
# or
pnpm add @classytic/formkit react-hook-form
# or
yarn add @classytic/formkit react-hook-form

Quick Start

1. Create Field Components

Each field component wraps your UI library with react-hook-form's Controller:

// components/form/form-input.tsx
"use client";

import { Controller } from "react-hook-form";
import type { FieldComponentProps } from "@classytic/formkit";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export function FormInput({ control, name, label, placeholder, required }: FieldComponentProps) {
  return (
    <Controller
      name={name}
      control={control}
      render={({ field, fieldState }) => (
        <div className="space-y-2">
          {label && (
            <Label htmlFor={name}>
              {label}
              {required && <span className="text-red-500 ml-1">*</span>}
            </Label>
          )}
          <Input {...field} id={name} placeholder={placeholder} />
          {fieldState.error && (
            <p className="text-sm text-red-500">{fieldState.error.message}</p>
          )}
        </div>
      )}
    />
  );
}

2. Create Form Adapter

Register your components and layouts:

// lib/form-adapter.tsx
"use client";

import { FormSystemProvider, type ComponentRegistry, type LayoutRegistry } from "@classytic/formkit";
import { FormInput } from "@/components/form/form-input";

const components: ComponentRegistry = {
  text: FormInput,
  email: FormInput,
  password: FormInput,
  // Add more field types...
};

const layouts: LayoutRegistry = {
  section: ({ title, description, children }) => (
    <div className="space-y-4">
      {title && <h3 className="text-lg font-semibold">{title}</h3>}
      {description && <p className="text-muted-foreground">{description}</p>}
      {children}
    </div>
  ),
  grid: ({ children, cols = 1 }) => (
    <div className={`grid grid-cols-${cols} gap-4`}>{children}</div>
  ),
};

export function FormProvider({ children }: { children: React.ReactNode }) {
  return (
    <FormSystemProvider components={components} layouts={layouts}>
      {children}
    </FormSystemProvider>
  );
}

3. Use FormGenerator

// app/signup/page.tsx
"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormGenerator, type FormSchema } from "@classytic/formkit";
import { FormProvider } from "@/lib/form-adapter";

// Validation schema
const signupSchema = z.object({
  firstName: z.string().min(2),
  lastName: z.string().min(2),
  email: z.string().email(),
  password: z.string().min(8),
});

type SignupData = z.infer<typeof signupSchema>;

// Form schema (type-safe!)
const formSchema: FormSchema<SignupData> = {
  sections: [
    {
      title: "Personal Information",
      cols: 2,
      fields: [
        { name: "firstName", type: "text", label: "First Name", required: true },
        { name: "lastName", type: "text", label: "Last Name", required: true },
      ],
    },
    {
      title: "Account",
      fields: [
        { name: "email", type: "email", label: "Email", required: true },
        { name: "password", type: "password", label: "Password", required: true },
      ],
    },
  ],
};

export default function SignupPage() {
  const form = useForm<SignupData>({
    resolver: zodResolver(signupSchema),
  });

  return (
    <FormProvider>
      <form onSubmit={form.handleSubmit(console.log)} className="space-y-8">
        <FormGenerator schema={formSchema} control={form.control} />
        <button type="submit">Sign Up</button>
      </form>
    </FormProvider>
  );
}

API Reference

FormGenerator

The main component that renders forms from a schema.

<FormGenerator
  schema={formSchema}      // Required: Form schema
  control={form.control}   // Required: React Hook Form control
  disabled={false}         // Optional: Disable all fields
  variant="default"        // Optional: Global variant
  className="my-form"      // Optional: Root element class
/>

FormSchema

interface FormSchema<T extends FieldValues = FieldValues> {
  sections: Section<T>[];
}

interface Section<T> {
  id?: string;              // Unique identifier
  title?: string;           // Section title
  description?: string;     // Section description
  icon?: ReactNode;         // Section icon
  fields?: BaseField<T>[];  // Fields in this section
  cols?: number;            // Grid columns (1-6)
  gap?: number;             // Grid gap
  variant?: string;         // Section variant
  className?: string;       // Custom class
  collapsible?: boolean;    // Make section collapsible
  defaultCollapsed?: boolean;
  condition?: (control) => boolean;  // Conditional rendering
  render?: (props) => ReactNode;     // Custom render function
}

BaseField

interface BaseField<T> {
  name: string;             // Field name (required)
  type: FieldType;          // Field type (required)
  label?: string;           // Field label
  placeholder?: string;     // Placeholder text
  helperText?: string;      // Helper text below field
  disabled?: boolean;       // Disable field
  required?: boolean;       // Mark as required
  readOnly?: boolean;       // Read-only field
  variant?: string;         // Field variant
  fullWidth?: boolean;      // Span full grid width
  className?: string;       // Custom class
  defaultValue?: unknown;   // Default value
  
  // Conditional rendering
  condition?: (formValues: T) => boolean;
  
  // For select/radio/checkbox
  options?: FieldOption[];
  
  // HTML input attributes
  min?: number | string;
  max?: number | string;
  step?: number;
  pattern?: string;
  minLength?: number;
  maxLength?: number;
  rows?: number;           // For textarea
  multiple?: boolean;      // For select/file
  accept?: string;         // For file input
  autoComplete?: string;
  autoFocus?: boolean;
  
  // Custom props
  [key: string]: unknown;
}

FieldComponentProps

Props passed to your field components:

interface FieldComponentProps<T extends FieldValues = FieldValues> extends BaseField<T> {
  field: BaseField<T>;     // Full field config
  control: Control<T>;     // React Hook Form control
  disabled?: boolean;      // Merged disabled state
  variant?: string;        // Active variant
}

ComponentRegistry

const components: ComponentRegistry = {
  // Simple mapping
  text: FormInput,
  select: FormSelect,
  
  // Variant-specific components
  compact: {
    text: CompactInput,
    select: CompactSelect,
  },
};

LayoutRegistry

const layouts: LayoutRegistry = {
  section: SectionLayout,
  grid: GridLayout,
  
  // Variant-specific layouts
  compact: {
    section: CompactSection,
  },
};

Advanced Features

Conditional Fields

{
  name: "companyName",
  type: "text",
  label: "Company Name",
  condition: (values) => values.accountType === "business",
}

Variants

Apply different styles based on context:

// Register variant-specific components
const components = {
  text: DefaultInput,
  compact: {
    text: CompactInput,
  },
};

// Use variant in schema
const schema = {
  sections: [{
    variant: "compact",  // All fields use compact variant
    fields: [...]
  }]
};

// Or per-field
{
  name: "notes",
  type: "text",
  variant: "compact",
}

Custom Section Render

{
  title: "Payment",
  render: ({ control, disabled }) => (
    <StripeElements>
      <CardElement />
      <FormInput name="billingName" control={control} />
    </StripeElements>
  ),
}

Grouped Select Options

{
  name: "country",
  type: "select",
  options: [
    {
      label: "North America",
      options: [
        { value: "us", label: "United States" },
        { value: "ca", label: "Canada" },
      ],
    },
    {
      label: "Europe",
      options: [
        { value: "uk", label: "United Kingdom" },
        { value: "de", label: "Germany" },
      ],
    },
  ],
}

Type Exports

import type {
  // Core
  FormSchema,
  FormGeneratorProps,
  BaseField,
  Section,
  
  // Components
  FieldComponentProps,
  FieldComponent,
  ComponentRegistry,
  
  // Layouts
  SectionLayoutProps,
  GridLayoutProps,
  LayoutComponent,
  LayoutRegistry,
  
  // Options
  FieldOption,
  FieldOptionGroup,
  
  // Utility types
  FieldType,
  LayoutType,
  Variant,
  DefineField,
  InferSchemaValues,
  SchemaFieldNames,
} from "@classytic/formkit";

Examples

See the example/shadcn directory for complete working examples with:

  • Form components (Input, Select, Checkbox)
  • Full adapter configuration
  • Zod validation
  • Conditional fields
  • Multi-column layouts
  • TypeScript integration

Browser Support

  • React 18.0+
  • React 19.0+
  • All modern browsers

License

MIT © Classytic

Links