Professional drag-and-drop form builder React component with 20+ field types, multi-step wizard, conditional logic, undo/redo, and JSON export
npm install @enerjisaformlibrary/formbuilder-reactProfessional drag-and-drop form builder React component with 20+ field types, multi-step wizard support, conditional logic, undo/redo history, and JSON export/import.
- 20+ Field Types: Text, textarea, number, email, phone, URL, password, date, time, date range, dropdown, checkbox, radio, toggle, multi-select, file upload, signature pad, star rating, slider, color picker, rich text editor, autocomplete, QR code, pattern format, and more
- Drag & Drop: Intuitive drag-and-drop interface for building forms
- Multi-Step Wizard: Create multi-step forms with progress indicator
- Conditional Logic: Show/hide/enable/disable fields based on conditions
- Undo/Redo: Full undo/redo support with 50-state history
- Grid Layout: 12-column responsive grid system
- Containers: Nested row/column layouts within containers
- JSON Export/Import: Export forms as JSON and import them back
- Custom Styling: Per-field CSS class customization
- Form Versioning: Save and restore form versions
- Tooltips: Help text support for fields
- Actions System: onClick, onChange, onFocus, onBlur event handlers
- Tab Index: Keyboard navigation order for form fields
``bash`
npm install @enerjisaformlibrary/formbuilder-react
`tsx
import { FormBuilder } from '@enerjisaformlibrary/formbuilder-react';
import '@enerjisaformlibrary/formbuilder-react/styles.css';
function App() {
const handleSave = (formSchema) => {
console.log('Form saved:', formSchema);
// Save to your database
};
const handleChange = (formSchema) => {
console.log('Form changed:', formSchema);
};
return (
onChange={handleChange}
/>
);
}
`
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| initialSchema | FormSchema | No | Initial form schema to load |onSave
| | (schema: FormSchema) => void | No | Called when user clicks Save button |onChange
| | (schema: FormSchema) => void | No | Called on every form change |
`typescript
interface FormSchema {
id: string;
name: string;
description?: string;
isMultiStep: boolean;
currentVersion: number;
rows: FormRow[]; // For single-page forms
steps?: FormStep[]; // For multi-step forms
settings?: FormSettings;
submissionConfig?: SubmissionConfig;
versions?: FormVersion[];
}
interface FormRow {
id: string;
columns: FormColumn[];
conditionalLogic?: ConditionalLogic;
}
interface FormColumn {
id: string;
width: number; // 1-12 (grid columns)
fields: FormField[];
responsiveWidth?: ResponsiveWidth;
}
interface FormField {
id: string;
type: FieldType;
props: FieldProps;
validation?: FieldValidation;
conditionalLogic?: ConditionalLogic;
customStyle?: CustomStyle;
}
`
- Single line text input
- textarea - Multi-line text area
- number - Numeric input
- email - Email input with validation
- password - Password input
- phone - Phone number input
- url - URL input$3
- date - Date picker
- time - Time picker
- daterange - Date range picker$3
- dropdown - Single select dropdown
- checkbox - Checkbox field
- radio - Radio button group
- toggle - Toggle switch
- multiselect - Multi-select with tags$3
- file - File upload with drag-drop
- signature - Signature pad with canvas drawing
- rating - Star rating (configurable max stars)
- slider - Range slider with min/max
- color - Color picker with hex input
- richtext - Rich text editor
- autocomplete - Autocomplete input
- pattern - Pattern format input (phone, credit card, etc.)
- qrcode - Static QR code display$3
- header - Section header
- label - Static label text
- paragraph - Paragraph text
- divider - Horizontal divider
- spacer - Vertical spacer
- alert - Alert/notification box
- image - Image placeholder
- button - Submit/reset/custom action buttons$3
- container - Grouping element with nested rows/columnsField Props
`typescript
interface FieldProps {
key: string; // Unique field key for form data
label?: string; // Field label
placeholder?: string; // Placeholder text
tooltip?: string; // Help tooltip text
optionsString?: string; // Options for dropdown/radio/checkbox (one per line)
size?: 'small' | 'medium' | 'large';
autoFocus?: boolean;
tabIndex?: number; // Keyboard navigation order (positive = order, -1 = skip, 0 = natural order)
htmlAttributes?: Record;
// Button specific
buttonConfig?: {
buttonType: 'submit' | 'reset' | 'button';
variant: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';
actionType?: 'submit' | 'reset' | 'navigate' | 'custom';
navigateUrl?: string;
customAction?: string;
};
// Pattern format specific
patternConfig?: {
format: 'phone' | 'creditCard' | 'date' | 'ssn' | 'custom';
mask?: string;
customPattern?: string;
};
// QR Code specific
qrCodeConfig?: {
value: string;
size: number;
};
// Container specific
containerConfig?: {
rows: ContainerRow[];
gap?: number;
padding?: number;
borderStyle?: 'none' | 'solid' | 'dashed';
};
// Rating specific
maxRating?: number;
// Slider specific
min?: number;
max?: number;
step?: number;
}
`Tab Index Usage
The
tabIndex property controls keyboard navigation order when users press Tab to move between form fields:| Value | Behavior |
|-------|----------|
| Positive (1, 2, 3...) | Field is focused in order from lowest to highest tabIndex |
| 0 | Field follows natural DOM order |
| -1 | Field is skipped during Tab navigation |
Example: To create a custom tab order:
- First Name: tabIndex = 1
- Email: tabIndex = 2
- Last Name: tabIndex = 3
- Phone: tabIndex = 4
When user presses Tab, they'll move: First Name → Email → Last Name → Phone
Important: tabIndex only works in Preview Mode. In editor mode, fields don't receive focus in the same way.
Validation
`typescript
interface FieldValidation {
required?: boolean;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
pattern?: string;
customMessage?: string;
}
`Conditional Logic
`typescript
interface ConditionalLogic {
enabled: boolean;
action: 'show' | 'hide' | 'enable' | 'disable' | 'require';
conditions: Condition[];
logicOperator: 'and' | 'or';
}interface Condition {
fieldKey: string;
operator: 'equals' | 'notEquals' | 'contains' | 'notContains' |
'greaterThan' | 'lessThan' | 'isEmpty' | 'isNotEmpty';
value: string;
}
`Custom Styling
`typescript
interface CustomStyle {
containerClassName?: string; // CSS class for field container
labelClassName?: string; // CSS class for label
inputClassName?: string; // CSS class for input element
css?: string; // Custom CSS (applied inline)
}
`Actions System
Each field can have event handlers:
`typescript
interface FieldActions {
onClick?: FieldAction;
onChange?: FieldAction;
onFocus?: FieldAction;
onBlur?: FieldAction;
}interface FieldAction {
type: 'showMessage' | 'hideField' | 'showField' | 'clearField' |
'setFieldValue' | 'focusField' | 'submitForm' | 'custom' | 'code';
args?: {
message?: string;
targetFieldKey?: string;
value?: string;
code?: string;
};
}
`Multi-Step Forms
`typescript
interface FormStep {
id: string;
title: string;
description?: string;
order: number;
rows: FormRow[];
validation?: {
validateOnNext?: boolean;
allowSkip?: boolean;
};
}
`Example: Complete Form Schema
`json
{
"id": "contact-form",
"name": "Contact Form",
"isMultiStep": false,
"currentVersion": 1,
"rows": [
{
"id": "row-1",
"columns": [
{
"id": "col-1",
"width": 6,
"fields": [
{
"id": "field-1",
"type": "input",
"props": {
"key": "firstName",
"label": "First Name",
"placeholder": "Enter your first name",
"tabIndex": 1
},
"validation": {
"required": true,
"minLength": 2
}
}
]
},
{
"id": "col-2",
"width": 6,
"fields": [
{
"id": "field-2",
"type": "input",
"props": {
"key": "lastName",
"label": "Last Name",
"placeholder": "Enter your last name",
"tabIndex": 2
},
"validation": {
"required": true
}
}
]
}
]
},
{
"id": "row-2",
"columns": [
{
"id": "col-3",
"width": 12,
"fields": [
{
"id": "field-3",
"type": "email",
"props": {
"key": "email",
"label": "Email Address",
"placeholder": "you@example.com",
"tooltip": "We'll never share your email",
"tabIndex": 3
},
"validation": {
"required": true
}
}
]
}
]
},
{
"id": "row-3",
"columns": [
{
"id": "col-4",
"width": 12,
"fields": [
{
"id": "field-4",
"type": "dropdown",
"props": {
"key": "country",
"label": "Country",
"optionsString": "USA\nCanada\nUK\nGermany\nFrance\nOther",
"tabIndex": 4
}
}
]
}
]
},
{
"id": "row-4",
"columns": [
{
"id": "col-5",
"width": 12,
"fields": [
{
"id": "field-5",
"type": "textarea",
"props": {
"key": "message",
"label": "Message",
"placeholder": "How can we help you?",
"tabIndex": 5
},
"validation": {
"required": true,
"minLength": 10,
"maxLength": 500
}
}
]
}
]
},
{
"id": "row-5",
"columns": [
{
"id": "col-6",
"width": 12,
"fields": [
{
"id": "field-6",
"type": "button",
"props": {
"key": "submit",
"label": "Submit",
"buttonConfig": {
"buttonType": "submit",
"variant": "primary",
"actionType": "submit"
}
}
}
]
}
]
}
]
}
`Integration with JetDesk
`tsx
import { FormBuilder } from '@enerjisaformlibrary/formbuilder-react';
import '@enerjisaformlibrary/formbuilder-react/styles.css';function FormEditor({ formId }) {
const [initialSchema, setInitialSchema] = useState(null);
useEffect(() => {
// Load existing form from database
fetch(
/api/forms/${formId})
.then(res => res.json())
.then(data => setInitialSchema(data.schema));
}, [formId]); const handleSave = async (schema) => {
await fetch(
/api/forms/${formId}, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ schema })
});
}; if (!initialSchema) return
Loading...; return (
initialSchema={initialSchema}
onSave={handleSave}
/>
);
}
``- Ctrl/Cmd + Z: Undo
- Ctrl/Cmd + Shift + Z: Redo
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
MIT