A powerful, flexible, and beautiful form builder for React applications built with React Hook Form, Zod, and Tailwind CSS.
✨ Reusable Field Components
- Text, Number, Date inputs (with React Day Picker calendar)
- Numeric String Field (string type but only accepts digits - for IDs, postal codes, etc.)
- Custom Select with single/multi-select support (with search)
- File Upload
- Radio groups and Checkboxes
- All with built-in validation support
🎨 Beautiful UI
- Built with Tailwind CSS
- Responsive grid layout system
- Clean and modern design
🔄 Conditional Logic
- Show/hide fields based on other field values
- Enable/disable fields dynamically
- Support for complex conditions (equals, notEquals, in, notIn, isEmpty, greaterThan, lessThan)
- Live reactive updates using React Hook Form's
watch()
📊 Multi-Step Forms
- Easy stepper/wizard forms
- Progress indicator
- Conditional steps
🌐 Dynamic Options
- Load select options from API
- Transform data on the fly
- Custom fetch functions
🏗️ Clean Architecture
- Uses FormProvider and useFormContext
- Modular and extensible
- TypeScript support
src/
├── form-engine/
│ ├── components/
│ │ ├── fields/
│ │ │ ├── TextField.tsx
│ │ │ ├── NumberField.tsx
│ │ │ ├── DateField.tsx
│ │ │ ├── SelectField.tsx
│ │ │ ├── FileField.tsx
│ │ │ ├── RadioField.tsx
│ │ │ ├── CheckboxField.tsx
│ │ │ └── index.ts
│ │ └── core/
│ │ ├── FieldRenderer.tsx
│ │ ├── FormSection.tsx
│ │ └── FormEngine.tsx
│ ├── types/
│ │ └── index.ts
│ ├── utils/
│ │ ├── conditionalLogic.ts
│ │ └── dynamicOptions.ts
│ └── index.ts
└── examples/
├── simpleFormSchema.ts
├── stepperFormSchema.ts
├── conditionalLogicSchema.ts
└── dynamicSelectSchema.ts
npm installnpm install react-hook-form @hookform/resolvers zod react-day-picker lucide-react
npm install -D tailwindcss postcss autoprefixer- react-hook-form - Form state management and validation
- zod - Schema validation
- react-day-picker - Beautiful date picker with calendar UI
- lucide-react - Icon library
- tailwindcss - Styling
import { FormEngine } from "./form-engine";
import { simpleFormSchema } from "./examples/simpleFormSchema";
function App() {
const handleSubmit = (data: any) => {
console.log("Form data:", data);
};
return (
<FormEngine
schema={simpleFormSchema}
onSubmit={handleSubmit}
submitButtonText="Submit"
/>
);
}import { FormEngine } from "./form-engine";
import { stepperFormSchema } from "./examples/stepperFormSchema";
function App() {
const handleSubmit = (data: any) => {
console.log("Form data:", data);
};
return (
<FormEngine
schema={stepperFormSchema}
onSubmit={handleSubmit}
showStepNavigation={true}
/>
);
}The Form Engine supports multiple ways to structure your forms:
const schema: FormSchema = {
fields: [
{ name: "firstName", type: "text", label: "First Name", cols: 6 },
{ name: "email", type: "text", label: "Email", cols: 6 },
],
};const schema: FormSchema = {
sections: [
{
title: "Personal Info",
fields: [
{ name: "firstName", type: "text", label: "First Name", cols: 6 },
],
},
{
title: "Contact",
fields: [{ name: "email", type: "text", label: "Email", cols: 12 }],
},
],
};const schema: FormSchema = {
steps: [
{
title: 'Step 1',
sections: [
{
title: 'Personal Details',
fields: [...],
},
],
},
],
};const schema: FormSchema = {
steps: [
{
title: "Step 1",
fields: [{ name: "firstName", type: "text", label: "First Name" }],
},
],
};const schema: FormSchema = {
steps: [
{
title: 'Step 1',
fields: [...], // Direct fields
},
{
title: 'Step 2',
sections: [...], // Organized in sections
},
],
};{
name: 'firstName',
label: 'First Name',
type: 'text',
placeholder: 'Enter your first name',
cols: 1, // Column span: 1 (half row), 2 or 12 (full row). Default: 1
// Mobile is always full width (1 field per row)
// Desktop: cols=1 means 2 fields per row, cols=2 or 12 means 1 field per row
}{
name: 'spouseName',
label: "Spouse's Name",
type: 'text',
cols: 12,
showWhen: {
field: 'isMarried',
equals: true,
},
}{
name: 'bankAccount',
label: 'Bank Account',
type: 'text',
cols: 6,
enableWhen: {
field: 'country',
equals: 'BD',
},
}{
name: 'dateOfBirth',
label: 'Date of Birth',
type: 'date',
placeholder: 'Select a date',
cols: 6,
min: '1900-01-01', // Minimum selectable date
max: '2024-12-31', // Maximum selectable date
validation: {
required: 'Date of birth is required',
},
}The date field uses React Day Picker for a beautiful calendar interface with:
- Visual calendar popup for date selection
- Min/max date restrictions
- Disabled dates support
- Formatted date display (e.g., "Dec 4, 2025")
- Click-outside to close functionality
{
name: 'skills',
label: 'Skills',
type: 'select',
placeholder: 'Select your skills',
cols: 6,
isMulti: true, // Enable multiple selection
options: [
{ label: 'JavaScript', value: 'js' },
{ label: 'TypeScript', value: 'ts' },
{ label: 'React', value: 'react' },
{ label: 'Node.js', value: 'node' },
],
validation: {
required: 'Please select at least one skill',
},
}Features:
- Multiple value selection with checkboxes
- Selected items displayed as tags with remove buttons
- Search functionality (for 5+ options)
- Keyboard navigation support
- Works with both static and dynamic options
{
name: 'user',
label: 'Select User',
type: 'select',
cols: 12,
dynamicOptions: {
url: 'https://api.example.com/users',
transform: (data: any[]) =>
data.map(user => ({
label: user.name,
value: user.id,
})),
},
}equals: Field value equals specified valuenotEquals: Field value does not equal specified valuein: Field value is in array of valuesnotIn: Field value is not in array of valuesisEmpty: Field is empty/null/undefinedisNotEmpty: Field has a valuegreaterThan: Numeric field is greater than valuelessThan: Numeric field is less than value
The Form Engine supports two validation approaches:
import { z } from 'zod';
const validationSchema = z.object({
firstName: z.string().min(2, 'First name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
age: z.number().min(18, 'Must be at least 18 years old'),
});
const formSchema: FormSchema = {
validationSchema,
fields: [...],
};const formSchema: FormSchema = {
fields: [
{
name: "firstName",
type: "text",
label: "First Name",
validation: {
required: true, // "This field is required"
minLength: {
value: 2,
message: "First name must be at least 2 characters",
},
},
},
{
name: "email",
type: "text",
label: "Email",
validation: {
required: "Email is required",
email: "Please enter a valid email",
},
},
{
name: "age",
type: "number",
label: "Age",
validation: {
required: true,
min: { value: 18, message: "Must be 18+" },
max: { value: 120, message: "Invalid age" },
},
},
{
name: "quantity",
type: "number",
label: "Quantity",
validation: {
required: true,
min: 1, // Simple min validation (default message)
max: 9999, // Simple max validation (default message)
},
},
],
};const schema = z.object({
name: z.string().min(2),
age: z.number().optional(),
});
const formSchema: FormSchema = {
validationSchema: schema, // Zod base validation
fields: [
{
name: "age",
type: "number",
label: "Age",
validation: {
// Additional field-level rules
min: { value: 18, message: "Must be 18+" },
},
},
],
};See FIELD_VALIDATION_GUIDE.md for detailed examples.
The NumberField component supports min/max validation through the validation prop:
{
name: "shares",
type: "number",
label: "Number of Shares",
validation: {
required: true,
min: { value: 1, message: "Minimum 1 share required" },
max: { value: 99999999, message: "Maximum 99,999,999 shares allowed" },
},
}
// Or use simple numbers for default messages
{
name: "age",
type: "number",
label: "Age",
validation: {
min: 18, // "Minimum value is 18"
max: 120, // "Maximum value is 120"
},
}Note: Use validation.min and validation.max for number validation. The min/max props directly on the field config are not used for validation.
{
title: 'Business Information',
showWhen: {
field: 'userType',
equals: 'business',
},
fields: [...],
}The form engine uses a 12-column grid system. You can specify how many columns each field should span:
// Half width (6 columns)
{ name: 'firstName', cols: 6, ... }
// Full width (12 columns)
{ name: 'email', cols: 12, ... }
// One-third width (4 columns)
{ name: 'age', cols: 4, ... }npm run devThen open your browser to see the demo with multiple form examples.
- Create a new field component in
src/form-engine/components/fields/ - Add the field type to
FieldTypeinsrc/form-engine/types/index.ts - Create a new interface extending
BaseFieldConfig - Add the component to
FieldRenderer.tsx - Export from
src/form-engine/components/fields/index.ts
Edit src/form-engine/utils/conditionalLogic.ts and add your operator to the evaluateCondition function.
MIT