Headless form builder which exposes client side and admin side hooks for multi-step form validation, and multi-step form creation.
npm install @jguerena15/form-builderA headless, type-safe React form builder package that provides comprehensive form functionality while allowing complete UI flexibility. Build complex forms with conditional logic, validation, and external data sources - all without being locked into a specific UI framework.
- 🎯 Headless Architecture - Pure business logic with no UI components
- 🔧 Builder & Renderer Hooks - Separate hooks for form creation and form filling
- 📝 Rich Question Types - Text, number, select, multi-select, date/time, slider, Likert scale, and more
- 🔀 Conditional Logic - Show/hide questions based on previous answers or external conditions
- ✅ Built-in Validation - Zod-powered runtime validation with custom rules
- 🔌 External Data Sources - Load dropdown options from APIs or databases
- 💾 Persistence Support - Save and resume form progress
- 📊 Progress Tracking - Track completion percentage and navigation state
- 🚀 TypeScript First - Full type safety and IntelliSense support
- ⚡ Performance Optimized - Memoization and efficient re-renders
``bash`
npm install @jguerena15/form-builderor
yarn add @jguerena15/form-builderor
pnpm add @jguerena15/form-builder
`tsx
import { useFormBuilder } from '@jguerena15/form-builder';
function FormBuilderInterface() {
const {
form,
addQuestion,
updateQuestion,
deleteQuestion,
addConditional,
save
} = useFormBuilder();
// Add a text input question
const handleAddTextQuestion = () => {
addQuestion({
id: 'name',
type: 'text-input',
label: 'What is your name?',
required: true,
order: 0
});
};
// Add conditional logic
const handleAddConditional = () => {
addConditional({
questionId: 'followup',
showIf: {
logic: 'AND',
conditions: [{
sourceType: 'question',
sourceId: 'name',
operator: 'not_equals',
value: ''
}]
}
});
};
// Your UI implementation
return (
$3
`tsx
import { useFormRenderer } from '@jguerena15/form-builder';function FormRenderer({ formDefinition }) {
const {
form,
currentQuestion,
handleNext,
handlePrevious,
handleSubmit,
canProceed,
progress,
isLastStep
} = useFormRenderer({
formDefinition,
onComplete: async (response) => {
console.log('Form completed!', response);
// Submit to your backend
}
});
// Your UI implementation
return (
Progress: {progress}% {currentQuestion && (
{/ Render input based on currentQuestion.type /}
)}
onClick={isLastStep ? handleSubmit : handleNext}
disabled={!canProceed}
>
{isLastStep ? 'Submit' : 'Next'}
);
}
`Advanced Features
$3
Load dropdown options from APIs or other external sources:
`tsx
const countryDataSource = {
id: 'countries',
name: 'Country List',
fetchOptions: async () => {
const response = await fetch('/api/countries');
const countries = await response.json();
return countries.map(country => ({
id: country.code,
label: country.name,
value: country.code
}));
}
};const { form } = useFormBuilder({
externalDataSources: [countryDataSource]
});
// Add a question that uses the data source
addQuestion({
id: 'country',
type: 'single-select',
label: 'Select your country',
dataSourceId: 'countries',
required: true
});
`$3
Control question visibility based on external state:
`tsx
const isAuthenticatedCondition = {
id: 'is-authenticated',
name: 'User Authentication Status',
getValue: () => userStore.isAuthenticated
};const { form } = useFormRenderer({
formDefinition,
externalConditions: [isAuthenticatedCondition]
});
`$3
Save and resume form progress:
`tsx
const { form } = useFormRenderer({
formDefinition,
persistence: {
save: async (response) => {
localStorage.setItem('form-progress', JSON.stringify(response));
},
load: async (formId) => {
const saved = localStorage.getItem('form-progress');
return saved ? JSON.parse(saved) : null;
}
},
autoSave: true // Auto-save after each question
});
`$3
Create sophisticated show/hide rules:
`tsx
addConditional({
questionId: 'premium-features',
showIf: {
logic: 'OR',
conditions: [
{
sourceType: 'question',
sourceId: 'plan',
operator: 'equals',
value: 'premium'
},
{
sourceType: 'external',
sourceId: 'is-beta-user',
operator: 'equals',
value: true
}
]
}
});
`Question Types
$3
`tsx
{
type: 'text-input',
minLength?: number,
maxLength?: number,
pattern?: string,
placeholder?: string
}
`$3
`tsx
{
type: 'number-input',
min?: number,
max?: number,
step?: number
}
`$3
`tsx
{
type: 'single-select',
options?: AnswerOption[],
dataSourceId?: string
}
`$3
`tsx
{
type: 'multi-select',
options?: AnswerOption[],
dataSourceId?: string,
minSelections?: number,
maxSelections?: number
}
`$3
`tsx
{
type: 'date-time-picker',
minDate?: string,
maxDate?: string,
includeTime?: boolean
}
`$3
`tsx
{
type: 'slider',
min: number,
max: number,
step?: number,
showValue?: boolean
}
`$3
`tsx
{
type: 'likert-scale',
minLabel?: string,
maxLabel?: string
}
`$3
`tsx
{
type: 'text-area',
minLength?: number,
maxLength?: number,
placeholder?: string
}
`API Reference
$3
`tsx
const {
form, // Current form definition
validationErrors, // Array of validation errors
updateMetadata, // Update form title, description, etc.
addQuestion, // Add a new question
updateQuestion, // Update existing question
deleteQuestion, // Delete a question
reorderQuestion, // Change question order
addConditional, // Add conditional logic
removeConditional, // Remove conditional logic
registerExternalCondition, // Register external condition
registerDataSource, // Register data source
fetchDataSourceOptions, // Fetch options from data source
save, // Save form definition
load, // Load form definition
reset // Reset to initial state
} = useFormBuilder(initialForm?, options?);
`$3
`tsx
const {
form, // React Hook Form instance
currentStep, // Current step index
totalSteps, // Total visible steps
currentQuestion, // Current question object
canProceed, // Can navigate forward
canGoBack, // Can navigate backward
handleNext, // Go to next question
handlePrevious, // Go to previous question
handleSubmit, // Submit form
progress, // Completion percentage
isFirstStep, // On first question
isLastStep, // On last question
isLoading, // Loading external data
isSaving, // Saving form data
errors, // Error messages
isValid, // Current input is valid
formResponse, // Current response data
resetForm // Reset form
} = useFormRenderer(options);
`Examples
Check out the
/demo directory for a complete example implementation with a working form builder and renderer.Development
`bash
Install dependencies
yarn installRun development demo
yarn devRun tests
yarn testBuild package
yarn buildType check
yarn type-check
``