Skip to content

Examples

Practical examples demonstrating common form builder patterns. Each example shows the code followed by a live interactive demo.

TIP

Only one live demo is shown at a time to keep the builder responsive. Use the tabs below the code samples to switch between demos.

Simple Contact Form

Build a basic contact form with name, email, and message fields:

ts
import { useBuilderStore } from '@banclo/jsonforms-core'

const store = useBuilderStore()

// Add a horizontal layout for first/last name
const layout = store.addNode('horizontal-layout', store.rootNode.id)

// Add name fields inside the horizontal layout
store.addNode('text-control', layout.id)  // => "text" field
store.addNode('text-control', layout.id)  // => "text1" field

// Rename the fields
const nodes = [...store.nodeMap.values()]
const textNode = nodes.find(n => n.propertyName === 'text')
const text1Node = nodes.find(n => n.propertyName === 'text1')

if (textNode) {
  store.updateNode(textNode.id, {
    propertyName: 'firstName',
    label: 'First Name',
    schemaProperties: { type: 'string', minLength: 1 },
  })
}

if (text1Node) {
  store.updateNode(text1Node.id, {
    propertyName: 'lastName',
    label: 'Last Name',
  })
}

// Add email field
const emailNode = store.addNode('text-control', store.rootNode.id)
store.updateNode(emailNode.id, {
  propertyName: 'email',
  label: 'Email',
  schemaProperties: { type: 'string', format: 'email' },
})

// Add message field
const messageNode = store.addNode('text-control', store.rootNode.id)
store.updateNode(messageNode.id, {
  propertyName: 'message',
  label: 'Message',
  schemaProperties: { type: 'string' },
  options: { multi: true },
})

Generated Output

JSON Schema:

json
{
  "type": "object",
  "properties": {
    "firstName": { "type": "string", "minLength": 1 },
    "lastName": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "message": { "type": "string" }
  },
  "required": ["firstName"]
}

UI Schema:

json
{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "HorizontalLayout",
      "elements": [
        { "type": "Control", "scope": "#/properties/firstName", "label": "First Name" },
        { "type": "Control", "scope": "#/properties/lastName", "label": "Last Name" }
      ]
    },
    { "type": "Control", "scope": "#/properties/email", "label": "Email" },
    { "type": "Control", "scope": "#/properties/message", "label": "Message", "options": { "multi": true } }
  ]
}

Tabbed Registration Form

Using Categorization and Category to create a tabbed form:

ts
const store = useBuilderStore()

// Replace root with a categorization
store.reset()

const cat = store.addNode('categorization', store.rootNode.id)

// Personal info tab
const tab1 = store.addNode('category', cat.id)
store.updateNode(tab1.id, { label: 'Personal Info' })

const name = store.addNode('text-control', tab1.id)
store.updateNode(name.id, {
  propertyName: 'fullName',
  label: 'Full Name',
  schemaProperties: { type: 'string', minLength: 1 },
})

const dob = store.addNode('date-control', tab1.id)
store.updateNode(dob.id, {
  propertyName: 'dateOfBirth',
  label: 'Date of Birth',
})

// Preferences tab
const tab2 = store.addNode('category', cat.id)
store.updateNode(tab2.id, { label: 'Preferences' })

const newsletter = store.addNode('boolean-control', tab2.id)
store.updateNode(newsletter.id, {
  propertyName: 'newsletter',
  label: 'Subscribe to newsletter',
})

const theme = store.addNode('enum-control', tab2.id)
store.updateNode(theme.id, {
  propertyName: 'theme',
  label: 'Preferred Theme',
  schemaProperties: { type: 'string', enum: ['light', 'dark', 'system'] },
})

Conditional Form with Rules

Show or hide fields based on user input:

ts
const store = useBuilderStore()

// Employment status dropdown
const status = store.addNode('enum-control', store.rootNode.id)
store.updateNode(status.id, {
  propertyName: 'employmentStatus',
  label: 'Employment Status',
  schemaProperties: {
    type: 'string',
    enum: ['employed', 'self-employed', 'unemployed', 'student'],
  },
})

// Company name -- shown only when employed
const company = store.addNode('text-control', store.rootNode.id)
store.updateNode(company.id, {
  propertyName: 'companyName',
  label: 'Company Name',
  rule: {
    effect: 'SHOW',
    condition: {
      type: 'LEAF',
      scope: '#/properties/employmentStatus',
      expectedValue: 'employed',
    },
  },
})

// Business name -- shown only when self-employed
const business = store.addNode('text-control', store.rootNode.id)
store.updateNode(business.id, {
  propertyName: 'businessName',
  label: 'Business Name',
  rule: {
    effect: 'SHOW',
    condition: {
      type: 'LEAF',
      scope: '#/properties/employmentStatus',
      expectedValue: 'self-employed',
    },
  },
})

// University -- shown only when student
const university = store.addNode('text-control', store.rootNode.id)
store.updateNode(university.id, {
  propertyName: 'university',
  label: 'University',
  rule: {
    effect: 'SHOW',
    condition: {
      type: 'LEAF',
      scope: '#/properties/employmentStatus',
      expectedValue: 'student',
    },
  },
})

Import and Edit Existing Schema

Load an existing form definition, modify it, and re-export:

ts
const store = useBuilderStore()

// Import existing schemas
const existingJsonSchema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    email: { type: 'string', format: 'email' },
  },
}

const existingUiSchema = {
  type: 'VerticalLayout',
  elements: [
    { type: 'Control', scope: '#/properties/name', label: 'Name' },
    { type: 'Control', scope: '#/properties/email', label: 'Email' },
  ],
}

store.importSchema(existingJsonSchema, existingUiSchema)

// The tree now has two controls -- add a phone number field
const phone = store.addNode('text-control', store.rootNode.id)
store.updateNode(phone.id, {
  propertyName: 'phone',
  label: 'Phone Number',
  schemaProperties: { type: 'string', pattern: '^\\+?[1-9]\\d{1,14}$' },
})

// Export the updated schemas
const { jsonSchema, uiSchema } = store.exportSchema()
console.log(JSON.stringify(jsonSchema, null, 2))
console.log(JSON.stringify(uiSchema, null, 2))

Live Demos

Validation Before Export

Always validate before saving to catch structural issues:

ts
import { useBuilderStore, validateSchemaNode, validateGeneratedSchemas } from '@banclo/jsonforms-core'

const store = useBuilderStore()

function exportWithValidation() {
  // Step 1: Validate the tree structure
  const treeResult = validateSchemaNode(store.rootNode)
  if (!treeResult.valid) {
    alert('Please fix the following errors:\n' + treeResult.errors.map(e => e.message).join('\n'))
    return null
  }

  // Step 2: Generate schemas
  const { jsonSchema, uiSchema } = store.exportSchema()

  // Step 3: Validate generated output
  const schemaResult = validateGeneratedSchemas(jsonSchema, uiSchema)
  if (!schemaResult.valid) {
    console.error('Schema validation failed:', schemaResult.errors)
    return null
  }

  // Step 4: Warn about non-critical issues
  const allWarnings = [...treeResult.warnings, ...schemaResult.warnings]
  if (allWarnings.length > 0) {
    console.warn('Warnings:', allWarnings.map(w => w.message))
  }

  return { jsonSchema, uiSchema }
}