Package Exports
- @classytic/formkit
Readme
@classytic/formkit
A headless, type-safe form generation engine for React 18+ and React 19. Build dynamic forms from schemas with full TypeScript support and zero UI opinions.
โจ Features
- ๐ฏ Headless & UI-Agnostic: Bring your own components (Shadcn, Material UI, Ant Design, or custom)
- ๐ Schema-Driven: Generate complex forms from simple JSON schemas
- ๐ Type-Safe: Built with TypeScript, full type inference and autocomplete
- โก Performance: Optimized rendering with React 18/19 concurrent features
- ๐จ Variants: Support multiple component styles (compact, default, custom)
- ๐ Conditional Fields: Show/hide fields based on form values
- ๐งฉ Composable: Modular architecture with pluggable components
- ๐ช React Hook Form: Built on top of the industry-standard form library
- ๐ฒ Tree-Shakeable: Only bundle what you use
๐ฆ Installation
npm install @classytic/formkit react-hook-form
# or
yarn add @classytic/formkit react-hook-form
# or
pnpm add @classytic/formkit react-hook-form๐ Quick Start
1. Create a Component Adapter
Map your UI components to form field types:
// form-adapter.tsx
import { FormSystemProvider } from "@classytic/formkit";
import { Input } from "@/components/ui/input";
import { Select } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
const components = {
text: Input,
email: Input,
select: Select,
checkbox: Checkbox,
// Add more field types as needed
};
const layouts = {
grid: ({ children, cols = 1 }) => (
<div className={`grid grid-cols-${cols} gap-4`}>{children}</div>
),
section: ({ title, description, children }) => (
<div className="space-y-4">
{title && <h3 className="text-lg font-semibold">{title}</h3>}
{description && <p className="text-sm text-muted-foreground">{description}</p>}
{children}
</div>
),
};
export function FormProvider({ children }: { children: React.ReactNode }) {
return (
<FormSystemProvider components={components} layouts={layouts}>
{children}
</FormSystemProvider>
);
}2. Define Your Schema
import type { FormSchema } from "@classytic/formkit";
const userFormSchema: FormSchema = {
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,
},
{
name: "email",
type: "email",
label: "Email",
fullWidth: true,
},
],
},
{
title: "Account Settings",
fields: [
{
name: "role",
type: "select",
label: "Role",
options: [
{ value: "user", label: "User" },
{ value: "admin", label: "Admin" },
],
},
{
name: "notifications",
type: "checkbox",
label: "Enable notifications",
// Conditional field - only show for admins
condition: (values) => values.role === "admin",
},
],
},
],
};3. Render Your Form
import { useForm } from "react-hook-form";
import { FormGenerator } from "@classytic/formkit";
import { FormProvider } from "./form-adapter";
function UserForm() {
const form = useForm();
const onSubmit = (data: any) => {
console.log("Form data:", data);
};
return (
<FormProvider>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormGenerator schema={userFormSchema} control={form.control} />
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}๐ API Reference
<FormSystemProvider>
The root provider that registers your components.
<FormSystemProvider components={componentsMap} layouts={layoutsMap}>
{children}
</FormSystemProvider>Props:
components: Object mapping field types to React componentslayouts: Object mapping layout types to React componentschildren: React children
<FormGenerator>
Renders forms from schemas.
<FormGenerator schema={schema} control={control} disabled={false} variant="default" />Props:
schema: Form schema objectcontrol?: React Hook Form control (optional if inside FormProvider)disabled?: Global disabled statevariant?: Component variant to use
Type Exports
import type {
FormSchema,
BaseField,
Section,
FieldComponentProps,
ComponentRegistry,
} from "@classytic/formkit";๐จ Advanced Usage
Variants
Support multiple UI styles:
const components = {
text: DefaultInput,
compact: {
text: CompactInput,
select: CompactSelect,
},
};
// Use compact variant
<FormGenerator schema={schema} variant="compact" />Conditional Fields
{
name: "vatNumber",
type: "text",
label: "VAT Number",
condition: (values) => values.country === "EU",
}Custom Renderers
{
title: "Custom Section",
render: ({ control, disabled }) => (
<div>Your custom JSX here</div>
),
}Full Width Fields
{
name: "description",
type: "textarea",
label: "Description",
fullWidth: true, // Spans all columns
}๐งช Testing
npm test๐ License
MIT ยฉ Classytic
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
๐ Links
๐ Related Packages
- @classytic/notifications - Multi-channel notification system
- @classytic/mongokit - Event-driven MongoDB repositories