Imagine building forms that adapt in real-time to user input, server data, or business logic changes—without writing a single line of component code. Picture an admin panel where non-technical users can define form structures, or a multi-tenant application where each client gets custom forms tailored to their workflow.
This isn't science fiction. It's the power of dynamic forms, and with Enforma's schema-driven architecture, it's simpler than you might think.
If you've ever found yourself copy-pasting form components, writing endless conditional rendering logic, or struggling to maintain forms that need to change frequently, dynamic forms are the solution you've been looking for.
Introduction: The Power of Dynamic Forms
What Are Dynamic Forms?
Dynamic forms are forms whose structure, fields, validation rules, and behavior are determined at runtime rather than being hardcoded in your components. Instead of defining form layouts in Vue templates, you describe them using JSON configurations that can be stored in databases, loaded from APIs, or even modified by end users.
// Traditional static form - hardcoded structure
<template>
<form>
<input name="firstName" />
<input name="lastName" />
<input name="email" />
<!-- Fixed structure, requires code changes to modify -->
</form>
</template>
// Dynamic form - runtime-defined structure
const formSchema = {
firstName: { type: 'field', label: 'First Name', required: true },
lastName: { type: 'field', label: 'Last Name', required: true },
email: { type: 'field', label: 'Email', rules: 'required|email' }
}
// Same form, but completely configurable without code changes
Why Dynamic Forms Are Powerful
1. Server-Driven UIs
Load form configurations from your backend, enabling you to modify forms without deploying new code.
2. User-Configurable Forms
Let non-technical users create and modify forms through admin interfaces.
3. Multi-Tenant Applications
Provide different form structures for different clients or use cases.
4. A/B Testing Forms
Easily test different form layouts and field configurations.
5. Conditional Complex Logic
Create forms that adapt based on user permissions, subscription levels, or business rules.
Use Cases: Where Dynamic Forms Shine
Content Management Systems
Challenge: Different content types need different input fields. Blog posts need titles and content, events need dates and locations, products need prices and categories.
Dynamic Solution:
// Load content type schemas from server
const blogPostSchema = {
title: { type: 'field', label: 'Title', rules: 'required' },
content: { type: 'field', label: 'Content', inputComponent: 'textarea' },
tags: { type: 'repeatable', subfields: { name: { label: 'Tag' } } }
}
const eventSchema = {
title: { type: 'field', label: 'Event Name', rules: 'required' },
startDate: { type: 'field', label: 'Start Date', inputComponent: 'datepicker' },
venue: { type: 'field', label: 'Venue', rules: 'required' },
capacity: { type: 'field', label: 'Max Attendees', inputComponent: 'number' }
}
const productSchema = {
name: { type: 'field', label: 'Product Name', rules: 'required' },
price: { type: 'field', label: 'Price', inputComponent: 'currency' },
variants: {
type: 'repeatable_table',
subfields: {
size: { label: 'Size' },
color: { label: 'Color' },
sku: { label: 'SKU', rules: 'required' }
}
}
}
// One component handles all content types
<EnformaSchema :schema="currentContentTypeSchema" />
Admin Dashboards
Challenge: Different user roles need different form fields. Super admins see all fields, managers see some, regular users see basic fields only.
Dynamic Solution:
// Server returns schema based on user permissions
const getUserProfileSchema = (userRole) => {
const baseSchema = {
firstName: { type: 'field', label: 'First Name', required: true },
lastName: { type: 'field', label: 'Last Name', required: true },
email: { type: 'field', label: 'Email', rules: 'required|email' }
}
if (userRole === 'manager' || userRole === 'admin') {
baseSchema.department = {
type: 'field',
label: 'Department',
inputComponent: 'select',
inputProps: { options: ['Sales', 'Marketing', 'Engineering'] }
}
}
if (userRole === 'admin') {
baseSchema.permissions = {
type: 'repeatable',
label: 'Permissions',
subfields: {
resource: { label: 'Resource', rules: 'required' },
actions: {
label: 'Actions',
inputComponent: 'multiselect',
inputProps: { options: ['read', 'write', 'delete'] }
}
}
}
}
return baseSchema
}
Survey and Form Builders
Challenge: Non-technical users need to create complex forms without developer intervention.
Dynamic Solution:
// Form builder saves this configuration
const surveySchema = {
intro_section: {
type: 'section',
title: 'Personal Information'
},
age: {
type: 'field',
section: 'intro_section',
label: 'What is your age?',
inputComponent: 'select',
inputProps: {
options: ['18-25', '26-35', '36-45', '46-55', '55+']
}
},
experience_section: {
type: 'section',
title: 'Work Experience',
if: '${form.values().age !== "18-25"}' // Conditional section
},
current_role: {
type: 'field',
section: 'experience_section',
label: 'Current Job Role',
if: '${form.values().age !== "18-25"}'
}
}
// Survey gets rendered without any hardcoded components
<EnformaSchema :schema="loadedSurveySchema" />
How Enforma Processes JSON Schemas
Basic Schema Structure
Enforma uses a simple but powerful schema format:
const schema = {
// SECTIONS - Group related fields
personal_info: {
type: 'section',
title: 'Personal Information',
titleComponent: 'h3'
},
// FIELDS - Individual form inputs
firstName: {
type: 'field',
section: 'personal_info', // Belongs to personal_info section
label: 'First Name',
required: true,
rules: 'required|min_length:2',
inputProps: { placeholder: 'Enter your first name' }
},
// REPEATABLE FIELDS - Dynamic arrays
skills: {
type: 'repeatable_table',
section: 'personal_info',
subfields: {
name: { label: 'Skill Name', rules: 'required' },
level: {
label: 'Level',
inputComponent: 'select',
inputProps: { options: ['Beginner', 'Intermediate', 'Advanced'] }
}
}
}
}
Schema Rendering Process
- Schema Parsing: Enforma analyzes the schema structure
- Component Resolution: Maps schema types to Vue components
- Dynamic Evaluation: Processes expressions and conditional logic
- Render Tree Creation: Builds the component tree
- Reactive Updates: Updates components when data changes
<template>
<!-- This single component renders the entire dynamic form -->
<Enforma
:schema="dynamicSchema"
:data="formData"
:rules="validationRules"
:context="formContext"
/>
</template>
<script setup>
// Schema can come from anywhere - API, database, user input
const dynamicSchema = await fetchFormSchema('user-profile')
// Data structure matches the schema field names
const formData = {
firstName: '',
skills: []
}
</script>
Real-World Example: Building a Dynamic Job Application Form
Let's build a comprehensive job application form that adapts based on the job type and applicant responses:
// This schema could be stored in a database and loaded dynamically
const jobApplicationSchema = {
// Basic Information Section
basic_section: {
type: 'section',
title: 'Basic Information',
titleComponent: 'h2'
},
firstName: {
type: 'field',
section: 'basic_section',
label: 'First Name',
required: true,
rules: 'required|min_length:2'
},
lastName: {
type: 'field',
section: 'basic_section',
label: 'Last Name',
required: true,
rules: 'required|min_length:2'
},
email: {
type: 'field',
section: 'basic_section',
label: 'Email Address',
required: true,
rules: 'required|email',
inputProps: { type: 'email' }
},
// Job-specific section that adapts based on job type
position_section: {
type: 'section',
title: 'Position Information',
titleComponent: 'h2'
},
jobType: {
type: 'field',
section: 'position_section',
label: 'Job Type',
required: true,
inputComponent: 'select',
inputProps: {
options: ['Frontend Developer', 'Backend Developer', 'Full Stack Developer', 'Designer', 'Manager']
}
},
// Technical Skills - Only for developer roles
technical_section: {
type: 'section',
title: 'Technical Skills',
titleComponent: 'h2',
if: '${["Frontend Developer", "Backend Developer", "Full Stack Developer"].includes(form.values().jobType)}'
},
programmingLanguages: {
type: 'repeatable_table',
section: 'technical_section',
if: '${["Frontend Developer", "Backend Developer", "Full Stack Developer"].includes(form.values().jobType)}',
subfields: {
language: {
label: 'Programming Language',
rules: 'required',
inputComponent: 'select',
inputProps: {
options: ['JavaScript', 'TypeScript', 'Python', 'Java', 'Go', 'PHP', 'C#']
}
},
experience: {
label: 'Years of Experience',
rules: 'required|numeric|gte:0',
inputComponent: 'number',
inputProps: { min: 0, max: 20 }
},
proficiency: {
label: 'Proficiency Level',
rules: 'required',
inputComponent: 'select',
inputProps: { options: ['Beginner', 'Intermediate', 'Advanced', 'Expert'] }
}
}
},
// Frontend-specific fields
frontendFrameworks: {
type: 'repeatable',
section: 'technical_section',
if: '${["Frontend Developer", "Full Stack Developer"].includes(form.values().jobType)}',
label: 'Frontend Frameworks',
subfields: {
framework: {
label: 'Framework',
rules: 'required',
inputComponent: 'select',
inputProps: { options: ['Vue.js', 'React', 'Angular', 'Svelte'] }
},
projects: {
label: 'Number of Projects',
rules: 'required|numeric|gte:1',
inputComponent: 'number'
}
}
},
// Backend-specific fields
backendTechnologies: {
type: 'repeatable_table',
section: 'technical_section',
if: '${["Backend Developer", "Full Stack Developer"].includes(form.values().jobType)}',
subfields: {
technology: {
label: 'Backend Technology',
rules: 'required',
inputComponent: 'select',
inputProps: { options: ['Node.js', 'Express', 'Django', 'Laravel', 'Spring Boot'] }
},
experience: {
label: 'Years',
rules: 'required|numeric|gte:0',
inputComponent: 'number'
}
}
},
// Design Skills - Only for designer role
design_section: {
type: 'section',
title: 'Design Skills',
titleComponent: 'h2',
if: '${form.values().jobType === "Designer"}'
},
designTools: {
type: 'repeatable',
section: 'design_section',
if: '${form.values().jobType === "Designer"}',
label: 'Design Tools',
subfields: {
tool: {
label: 'Design Tool',
rules: 'required',
inputComponent: 'select',
inputProps: { options: ['Figma', 'Sketch', 'Adobe XD', 'Photoshop', 'Illustrator'] }
},
proficiency: {
label: 'Proficiency',
rules: 'required',
inputComponent: 'select',
inputProps: { options: ['Beginner', 'Intermediate', 'Advanced', 'Expert'] }
}
}
},
portfolio: {
type: 'field',
section: 'design_section',
if: '${form.values().jobType === "Designer"}',
label: 'Portfolio URL',
rules: 'required|url',
inputProps: { placeholder: 'https://your-portfolio.com' }
},
// Management Experience - Only for manager role
management_section: {
type: 'section',
title: 'Management Experience',
titleComponent: 'h2',
if: '${form.values().jobType === "Manager"}'
},
teamSize: {
type: 'field',
section: 'management_section',
if: '${form.values().jobType === "Manager"}',
label: 'Largest Team Size Managed',
rules: 'required|integer|gte:1',
inputComponent: 'number',
inputProps: { min: 1 }
},
managementYears: {
type: 'field',
section: 'management_section',
if: '${form.values().jobType === "Manager"}',
label: 'Years of Management Experience',
rules: 'required|numeric|gte:0',
inputComponent: 'number',
inputProps: { min: 0 }
},
// Experience Section - Always shown
experience_section: {
type: 'section',
title: 'Work Experience',
titleComponent: 'h2'
},
workExperience: {
type: 'repeatable',
section: 'experience_section',
min: 1,
max: 10,
subfields: {
company: {
label: 'Company',
rules: 'required'
},
position: {
label: 'Position',
rules: 'required'
},
startDate: {
label: 'Start Date',
rules: 'required|date',
inputComponent: 'datepicker'
},
endDate: {
label: 'End Date',
rules: 'date|date_after:@workExperience.*.startDate',
inputComponent: 'datepicker'
},
current: {
label: 'Current Position',
inputComponent: 'checkbox'
},
responsibilities: {
label: 'Key Responsibilities',
inputComponent: 'textarea',
inputProps: { rows: 4 }
}
}
}
}
Using the Dynamic Schema
<template>
<div class="job-application">
<h1>Job Application Form</h1>
<Enforma
:schema="jobApplicationSchema"
:data="applicationData"
:context="formContext"
:submit-handler="submitApplication"
/>
</div>
</template>
<script setup>
// The entire form is driven by the schema
const applicationData = {
firstName: '',
lastName: '',
email: '',
jobType: '',
programmingLanguages: [],
frontendFrameworks: [],
backendTechnologies: [],
designTools: [],
portfolio: '',
teamSize: null,
managementYears: null,
workExperience: [
{
company: '',
position: '',
startDate: '',
endDate: '',
current: false,
responsibilities: ''
}
]
}
// Context provides additional data and functions
const formContext = {
// Could include user permissions, API endpoints, etc.
userRole: 'applicant',
availablePositions: ['Frontend Developer', 'Backend Developer']
}
const submitApplication = async (data) => {
console.log('Submitting application:', data)
// Submit to your API
return true
}
</script>
Adding Validation to Dynamic Fields
Schema-Based Validation
Validation rules can be embedded directly in the schema:
const dynamicFormSchema = {
email: {
type: 'field',
label: 'Email',
rules: 'required|email',
messages: {
required: 'Email is required',
email: 'Please enter a valid email address'
}
},
password: {
type: 'field',
label: 'Password',
inputProps: { type: 'password' },
rules: 'required|min_length:8|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)/',
messages: {
required: 'Password is required',
min_length: 'Password must be at least 8 characters',
regex: 'Password must contain lowercase, uppercase and number'
}
},
confirmPassword: {
type: 'field',
label: 'Confirm Password',
inputProps: { type: 'password' },
rules: 'required|same:@password',
messages: {
required: 'Please confirm your password',
same: 'Passwords must match'
}
},
// Array validation
skills: {
type: 'repeatable',
subfields: {
name: {
label: 'Skill Name',
rules: 'required|min_length:2',
messages: {
required: 'Skill name is required'
}
},
level: {
label: 'Skill Level',
rules: 'required|in_list:beginner,intermediate,advanced',
inputComponent: 'select',
inputProps: { options: ['beginner', 'intermediate', 'advanced'] }
}
}
}
}
Dynamic Validation Rules
Create validation rules that adapt based on form state:
const adaptiveValidationSchema = {
userType: {
type: 'field',
label: 'User Type',
inputComponent: 'select',
inputProps: { options: ['individual', 'business'] },
rules: 'required'
},
// Individual users need personal info
firstName: {
type: 'field',
label: 'First Name',
if: '${form.values().userType === "individual"}',
rules: 'required_if:userType,individual'
},
lastName: {
type: 'field',
label: 'Last Name',
if: '${form.values().userType === "individual"}',
rules: 'required_if:userType,individual'
},
// Business users need company info
companyName: {
type: 'field',
label: 'Company Name',
if: '${form.values().userType === "business"}',
rules: 'required_if:userType,business'
},
taxId: {
type: 'field',
label: 'Tax ID',
if: '${form.values().userType === "business"}',
rules: 'required_if:userType,business|regex:/^[0-9]{2}-[0-9]{7}$/',
messages: {
regex: 'Tax ID must be in format XX-XXXXXXX'
}
}
}
Handling Schema Changes Dynamically
Live Schema Updates
Update schemas in real-time based on user actions or external events:
<template>
<div>
<!-- Admin controls -->
<div class="admin-panel" v-if="isAdmin">
<h3>Form Configuration</h3>
<button @click="addField">Add Field</button>
<button @click="toggleSection('skills')">Toggle Skills Section</button>
</div>
<!-- Dynamic form that updates live -->
<Enforma
:schema="liveSchema"
:data="formData"
:key="schemaVersion"
/>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const schemaVersion = ref(0)
const liveSchema = reactive({
name: {
type: 'field',
label: 'Name',
required: true
}
})
const addField = () => {
const fieldName = `dynamicField${Date.now()}`
liveSchema[fieldName] = {
type: 'field',
label: 'Dynamic Field',
required: false
}
schemaVersion.value++
}
const toggleSection = (sectionName) => {
if (liveSchema[sectionName]) {
delete liveSchema[sectionName]
} else {
liveSchema[sectionName] = {
type: 'section',
title: 'Skills Section'
}
}
schemaVersion.value++
}
</script>
Server-Driven Schema Updates
Load and update schemas from your backend:
// API-driven schema management
class DynamicFormManager {
constructor() {
this.schema = reactive({})
this.formData = reactive({})
}
async loadSchema(formId) {
try {
const response = await fetch(`/api/forms/${formId}/schema`)
const schemaData = await response.json()
// Replace current schema
Object.assign(this.schema, schemaData.schema)
Object.assign(this.formData, schemaData.defaultData)
return true
} catch (error) {
console.error('Failed to load schema:', error)
return false
}
}
async updateField(fieldName, fieldConfig) {
// Update locally first for immediate feedback
this.schema[fieldName] = fieldConfig
// Sync with server
try {
await fetch(`/api/forms/schema/field`, {
method: 'PATCH',
body: JSON.stringify({ fieldName, fieldConfig })
})
} catch (error) {
console.error('Failed to sync field update:', error)
}
}
async addConditionalField(condition, fieldConfig) {
const fieldName = `conditional_${Date.now()}`
this.schema[fieldName] = {
...fieldConfig,
if: condition
}
// Sync with server
await this.syncSchema()
}
async syncSchema() {
await fetch('/api/forms/schema', {
method: 'PUT',
body: JSON.stringify({ schema: this.schema })
})
}
}
// Usage
const formManager = new DynamicFormManager()
await formManager.loadSchema('user-registration')
Schema Versioning and Migration
Handle schema changes gracefully:
class SchemaVersionManager {
constructor() {
this.migrations = {
'1.0.0': this.migrateToV1,
'1.1.0': this.migrateToV1_1,
'2.0.0': this.migrateToV2
}
}
async loadSchema(formId) {
const schemaData = await fetch(`/api/forms/${formId}`).then(r => r.json())
// Check if migration is needed
if (schemaData.version !== this.currentVersion) {
return this.migrateSchema(schemaData)
}
return schemaData.schema
}
migrateSchema(schemaData) {
let { schema, version } = schemaData
// Apply migrations in order
for (const [targetVersion, migrationFn] of Object.entries(this.migrations)) {
if (this.isVersionNewer(targetVersion, version)) {
schema = migrationFn(schema)
version = targetVersion
}
}
return schema
}
migrateToV1_1(schema) {
// Example: Convert old 'type' field to new 'inputComponent'
Object.keys(schema).forEach(key => {
const field = schema[key]
if (field.type === 'field' && field.fieldType) {
field.inputComponent = field.fieldType
delete field.fieldType
}
})
return schema
}
migrateToV2(schema) {
// Example: Restructure validation rules
Object.keys(schema).forEach(key => {
const field = schema[key]
if (field.validation) {
field.rules = field.validation.join('|')
delete field.validation
}
})
return schema
}
}
Advanced Ideas: Plugin-Based Form Extensions
Custom Field Types
Extend Enforma with your own field types:
// Register custom field type
const customFieldTypes = {
'location-picker': {
component: LocationPickerField,
defaultProps: {
apiKey: process.env.GOOGLE_MAPS_API_KEY
}
},
'rich-text-editor': {
component: RichTextEditor,
defaultProps: {
toolbar: ['bold', 'italic', 'link']
}
},
'signature-pad': {
component: SignaturePad,
defaultProps: {
width: 400,
height: 200
}
}
}
// Use in schema
const extendedSchema = {
address: {
type: 'field',
label: 'Your Location',
inputComponent: 'location-picker',
inputProps: {
defaultLocation: 'San Francisco, CA'
}
},
description: {
type: 'field',
label: 'Description',
inputComponent: 'rich-text-editor',
inputProps: {
minLength: 100
}
},
signature: {
type: 'field',
label: 'Digital Signature',
inputComponent: 'signature-pad',
required: true
}
}
Schema Plugins System
Create a plugin system for schema transformations:
class SchemaPluginSystem {
constructor() {
this.plugins = []
}
addPlugin(plugin) {
this.plugins.push(plugin)
}
processSchema(schema, context) {
return this.plugins.reduce((processedSchema, plugin) => {
return plugin.transform(processedSchema, context)
}, schema)
}
}
// Example plugins
const conditionalFieldsPlugin = {
name: 'conditional-fields',
transform(schema, context) {
// Add conditional logic based on user permissions
if (!context.user.canEditAdvanced) {
Object.keys(schema).forEach(key => {
if (schema[key].advanced) {
delete schema[key]
}
})
}
return schema
}
}
const localizationPlugin = {
name: 'localization',
transform(schema, context) {
// Translate labels based on user locale
const locale = context.user.locale || 'en'
Object.keys(schema).forEach(key => {
const field = schema[key]
if (field.label && field.translations) {
field.label = field.translations[locale] || field.label
}
})
return schema
}
}
// Usage
const pluginSystem = new SchemaPluginSystem()
pluginSystem.addPlugin(conditionalFieldsPlugin)
pluginSystem.addPlugin(localizationPlugin)
const processedSchema = pluginSystem.processSchema(originalSchema, {
user: { canEditAdvanced: false, locale: 'es' }
})
Complete Example: Multi-Tenant Survey Platform
Here's a comprehensive example showing all concepts together:
<template>
<div class="survey-platform">
<div class="survey-header">
<h1>{{ surveyTitle }}</h1>
<p>{{ surveyDescription }}</p>
</div>
<!-- Dynamic form renders based on loaded schema -->
<Enforma
:schema="processedSchema"
:data="responseData"
:context="surveyContext"
:submit-handler="submitSurvey"
@field-change="handleFieldChange"
>
<!-- Custom slot for special field types -->
<template #field(rating)="{ field }">
<StarRating
:value="field.value"
:max="field.inputProps.max || 5"
@input="field.events.input"
/>
</template>
</Enforma>
<!-- Progress indicator -->
<div class="survey-progress">
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: `${progressPercent}%` }"
></div>
</div>
<span>{{ progressPercent }}% Complete</span>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
const props = defineProps({
surveyId: { type: String, required: true },
userId: { type: String, required: true }
})
// Reactive state
const surveyTitle = ref('')
const surveyDescription = ref('')
const originalSchema = reactive({})
const responseData = reactive({})
const processedSchema = computed(() => processSchemaWithLogic(originalSchema))
// Survey context for dynamic expressions
const surveyContext = reactive({
userId: props.userId,
startTime: Date.now(),
// Helper functions for schema expressions
calculateAge(birthYear) {
return new Date().getFullYear() - birthYear
},
hasAnswered(fieldName) {
return !!responseData[fieldName]
},
getConditionalQuestions(category) {
// Load additional questions based on previous answers
return this.questionBank[category] || []
}
})
// Progress calculation
const progressPercent = computed(() => {
const totalFields = Object.keys(processedSchema.value).filter(
key => processedSchema.value[key].type === 'field'
).length
const answeredFields = Object.values(responseData).filter(
value => value !== null && value !== '' && value !== undefined
).length
return totalFields > 0 ? Math.round((answeredFields / totalFields) * 100) : 0
})
// Load survey configuration
onMounted(async () => {
try {
const surveyData = await fetch(`/api/surveys/${props.surveyId}`).then(r => r.json())
surveyTitle.value = surveyData.title
surveyDescription.value = surveyData.description
// Process schema with tenant-specific rules
Object.assign(originalSchema, surveyData.schema)
// Initialize response data
initializeResponseData(surveyData.schema)
} catch (error) {
console.error('Failed to load survey:', error)
}
})
// Initialize form data based on schema
const initializeResponseData = (schema) => {
Object.keys(schema).forEach(key => {
const field = schema[key]
if (field.type === 'field') {
responseData[key] = field.defaultValue || null
} else if (field.type === 'repeatable') {
responseData[key] = []
}
})
}
// Process schema with conditional logic
const processSchemaWithLogic = (schema) => {
const processed = { ...schema }
// Add conditional fields based on previous answers
if (responseData.experienceLevel === 'advanced') {
processed.advancedQuestions = {
type: 'section',
title: 'Advanced Questions'
}
processed.technicalChallenge = {
type: 'field',
section: 'advancedQuestions',
label: 'Describe your biggest technical challenge',
inputComponent: 'textarea',
required: true
}
}
return processed
}
// Handle field changes for dynamic behavior
const handleFieldChange = ({ fieldName, value }) => {
console.log(`Field ${fieldName} changed to:`, value)
// Trigger conditional logic
if (fieldName === 'country') {
updateStateOptions(value)
}
if (fieldName === 'age' && value > 65) {
addSeniorDiscountField()
}
}
// Dynamic form modifications
const updateStateOptions = (country) => {
if (originalSchema.state) {
const stateOptions = getStatesByCountry(country)
originalSchema.state.inputProps.options = stateOptions
}
}
const addSeniorDiscountField = () => {
if (!originalSchema.seniorDiscount) {
originalSchema.seniorDiscount = {
type: 'field',
label: 'Apply Senior Discount',
inputComponent: 'checkbox',
defaultValue: true
}
responseData.seniorDiscount = true
}
}
// Submit survey response
const submitSurvey = async (data) => {
try {
const response = await fetch(`/api/surveys/${props.surveyId}/responses`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: props.userId,
responses: data,
completedAt: new Date().toISOString(),
timeTaken: Date.now() - surveyContext.startTime
})
})
if (response.ok) {
alert('Survey submitted successfully!')
return true
} else {
throw new Error('Submission failed')
}
} catch (error) {
console.error('Survey submission error:', error)
alert('Failed to submit survey. Please try again.')
return false
}
}
</script>
Final Thoughts: The Future of Form Development
Dynamic forms represent a fundamental shift in how we approach form development. Instead of writing components for every possible form variation, we define flexible schemas that can adapt to any requirement.
The Benefits You Gain
✓ Rapid Development: Create complex forms in minutes, not hours
✓ Non-Technical Empowerment: Allow business users to modify forms
✓ Consistent UI: Maintain design system compliance automatically
✓ Easy Maintenance: Update forms without code deployment
✓ Enhanced Flexibility: Adapt forms based on any criteria
✓ Better Testing: Test form logic separately from presentation
Best Practices for Dynamic Forms
- Start Simple: Begin with basic schemas and add complexity gradually
- Plan Your Schema Structure: Design schemas that are intuitive and maintainable
- Use Validation Liberally: Embed validation rules in schemas for consistency
- Implement Graceful Fallbacks: Handle schema loading errors elegantly
- Version Your Schemas: Plan for schema evolution and migration
- Test Thoroughly: Validate schemas in different contexts and configurations
Advanced Extensions
The possibilities with dynamic forms are endless:
- AI-Generated Forms: Use machine learning to generate optimal form structures
- A/B Testing Integration: Automatically test different form configurations
- Analytics Integration: Track form performance and optimize based on data
- Multi-Language Support: Dynamically translate forms based on user locale
- Accessibility Enhancements: Automatically apply accessibility improvements
Ready to Transform Your Forms?
Dynamic forms with Enforma eliminate the traditional boundaries between developers and business requirements. Whether you're building admin panels, survey platforms, or complex data entry systems, schema-driven forms provide the flexibility and power you need.
Start building your first dynamic form today:
npm install @encolajs/enforma
Explore Dynamic Form Examples →
The future of form development is dynamic, flexible, and powerful. With Enforma's schema-driven architecture, that future is available today.