Custom Elements

Learn how to create custom elements in the builder.

Builder vs Vueform Elements

If you’re familiar with Vueform as a form-rendering engine, you already know the different element types such as text, textarea, date, signature, and more. In the builder, these Vueform elements are used as the foundation to create builder elements. A builder element includes a schema property (explained in more detail later) that defines the Vueform element type and its default configuration options, like label, description, and others.

Multiple builder elements can utilize the same Vueform element type with varying default options. For instance, different builder elements such as Text Input, Number Input, Email Address, Password, and URL all rely on the Vueform text element type. However, each is customized through specific properties. For example, the Number Input element has inputType: 'number' and rules: ['nullable', 'numeric'], which turns it into a 'Number Input' element.

Creating Custom Builder Elements

When creating a custom builder element, there are typically two approaches:

  1. Using Existing Vueform Element Types: Leverage existing Vueform elements like text or textarea and extend them to create new builder elements with custom configurations.

  2. Creating New Vueform Element Types: Develop an entirely new Vueform element type and integrate it within the builder.

The following sections will explain both scenarios in detail.

Element Definition

Elements can be registered in builder.config.js under element.types:

js
// builder.config.js

import { defineConfig } from '@vueform/builder'

export default defineConfig({
  element: {
    types: {
      myType: {
        label: 'My element',
        description: 'My awesome element',
        icon: 'https://domain.com/my-element-icon.svg',
        category: 'static',
        schema: {
          type: 'my-type',
        },
        rules: [ /* ... */ ],
        operators: [ /* ... */ ],
        sections: { /* ... */},
        separators: { /* ... */ },
      },
      // ...
    }
  }
})

NameTypeDescription
[key]stringThe object key of the element type (myType in the example).
label*stringThe label of the element in the element list.
description*stringThe description of the element in the element list.
iconstringThe url of the element's icon in the element list (if left empty a default icon will be displayed). It's recommended to use SVG to keep the UI high quality on each display.
categorystringUnder which category should the element appear in the element list (if using categories).
schema*objectThe schema of the element that will be added.
rulesarrayThe list of validation rules the element config panel should have if it has validation.
operatorsstringDefines the list of operators that should be available in the dropdown list when the element is targeted with conditions. If left empty is empty and is not empty options will appear.
sections*string|objectThis determines what configuration options the element has when selected. It can be a string with the name of an other element which's sections should be reused (eg 'text') or an object containing custom configuration options (more about it later).
separators*string|objectDefines the separators between of configuration options (more about it later).

Overriding Existing Element Types

All builder elements that you can find on the left panel by default are stored in elementTypes export of @vueform/builder:

js
import { elementTypes } from '@vueform/builder'

console.log(elementTypes)

/**
 * Logs: 
 * 
 * captcha: { label: '...', description: '...', icon: [...],  ... },
 * checkbox: { label: '...', description: '...', icon: [...],  ... },
 * checkboxBlocks: { label: '...', description: '...', icon: [...],  ... },
 * checkboxTabs: { label: '...', description: '...', icon: [...],  ... },
 * checkboxgroup: { label: '...', description: '...', icon: [...],  ... },
 * ....
 * verticalSlider: { label: '...', description: '...', icon: [...],  ... },
 */

Elements define a schema property, an object that contains all the default options for the element (like type, label, description, etc.). Here's how schema looks like for select type:

js
import { elementTypes } from '@vueform/builder'

console.log('select: ', elementTypes.select)

/**
 * Logs: 
 * 
 * select: {
 *   label: 'Select',
 *   description: 'Select input',
 *   icon: ['fas', 'caret-square-down'],
 *   category: 'fields',
 *   schema: {
 *     type: 'select',
 *     items: [
 *       { value: 0, label: 'Label' },
 *     ],
 *     search: true,
 *     native: false,
 *     label: 'select_field_label',
 *     inputType: 'search',
 *     autocomplete: 'off',
 *   }
 * }
 */

If we want to customize it, eg. change the default items we can do so by overriding element.types.select.schema.items in builder.config.js:

js
// builder.config.js

import { defineConfig, elementTypes } from '@vueform/builder'

export default defineConfig({
  element: {
    types: {
      select: {
        ...elementTypes.select,
        schema: {
          ...elementTypes.select.schema,
          items: [
            { value: 'apply', label: 'Apple' },
            { value: 'orange', label: 'Orange' },
            { value: 'pear', label: 'Pear' },
          ]
        }
      }
    }
  }
})

If we have a localized builder we can take this a step further and provide translation tags instead of item labels as they are automatically resolved from the current locale:

js
// builder.config.js

import {
  defineConfig,
  elementTypes,
  en_US,
  nl_NL,
} from '@vueform/builder'

en_US.select_default_apple = 'Apple'
en_US.select_default_orange = 'Orange'
en_US.select_default_pear = 'Pear'

nl_NL.select_default_apple = 'Appel'
nl_NL.select_default_orange = 'Sinaasappel'
nl_NL.select_default_pear = 'Peer'

export default defineConfig({
  element: {
    types: {
      select: {
        ...elementTypes.select,
        schema: {
          ...elementTypes.select.schema,
          items: [
            { value: 'apply', label: 'select_default_apple' },
            { value: 'orange', label: 'select_default_orange' },
            { value: 'pear', label: 'select_default_pear' },
          ]
        }
      }
    }
  },
  builderLocales: {
    en_US,
    nl_NL,
  },
})

We can use the same method to override any property of an element including label, description, icon, category or even sections and separators.

In the next chapter we will learn about Custom Config Panels that will reveal how we can customize sections and separators.

Reusing Existing Element Types

We can not only override existing elements, but use the elementTypes to add new elements similar to an existing element.

Let's say we want to use the select element type and create a User selector element type, that automatically fetches all the available users from our server:

js
// builder.config.js

import { defineConfig, elementTypes } from '@vueform/builder'

export default defineConfig({
  element: {
    types: {
      user: {
        ...elementTypes.select,
        label: 'User selector',
        description: 'Select a user from the database',
        icon: 'https://domain.com/user-element-icon.svg',
        category: 'fields',
        schema: {
          type: 'select',
          label: 'Users',
          native: false,
          search: true,
          resolveOnLoad: false,
          minChars: 2,
          delay: 300,
          inputType: 'search',
          autocomplete: 'off',
          items: '/api/users',
        },
      },
    },
  },
})

We've got an element that is identical to the original select type only that its properties and default schema are customized.

Now the only thing left is to add this new user element to the list of available elements. Continue reading to learn how.

Registering Elements & Display Order

To make an element available/unavailable in the builder or change the order of existing elements we can override the elements property in builder.config.js.

The full list of available elements is available here. We can use this list to create a customized list of elements we want to make available. Here's a customized list with our user element type included after number:

js
// builder.config.js

import { defineConfig } from '@vueform/builder'

export default defineConfig({
  element: {
    types: {
      user: {
        // ... `user` type from previous example 
      }
    }
  },

  elements: [
    'text',
    'number',
    'user', // <-- "user" type added here so it will be displayed after "Number input"
    'location',
    'textarea',
    // ...
  ]
})

Customizing Config Panel

Please head to Custom Config Panels chapter to learn how to customize the config panel of your new element.

Disabling Element Features

Element features such as editing or removing can be disabled by adding a builder object to the element schema:

js
// builder.config.js

import { defineConfig } from '@vueform/builder'

export default defineConfig({
  element: {
    types: {
      user: {
        // ...
        schema: {
          type: 'select',
          label: 'Users',
          // ...
          builder: {
            remove: false,  // Disables removing the element
            clone: false,   // Disables cloning the element
            move: false,    // Disables moving the element
            edit: false,    // Disables editing the element
            resize: false,  // Disables resizing the element
          }
        },
      },
    }
  }
})

Containers and lists containing an element with disabled features will also have their features disabled. This is true for each from above except for edit.

Defining Condition Operators

We can define the list of operators that should be available when the element is targeted in the condition selector modal.

js
// ...

export default {
  element: {
    types: {
      user: {
        // ...
        schema: {
          type: 'select',
          label: 'Users',
          // ...
        },
        operators: [
          'is empty',
          'is not empty',
          'is equal to',
          'is not equal to',
        ]
      },
    },
  },
},

The full list of operators available:

  • is empty
  • is not empty
  • is equal to
  • is not equal to
  • > than
  • >= than
  • < than
  • <= than
  • starts with
  • ends with
  • contains

Operators might be provided as objects, eg:

  • { value: 'is equal to', label: 'is any equal to' }
  • { value: 'is not equal to', label: 'is not equal to any' }

Creating a New Element Type

If we want to add a custom element, which is not based on an existing Vueform element (like text, textarea, select, etc.), first we have to create it and register it for Vueform based on the docs in Vueform's Creating Elements section.

Let's create a new element called LogoElement (type logo) which only job is to display a logo in either black & white or colored format:

vue
<!-- LogoElement.vue -->

<template>
  <ElementLayout>
    <template #element>
      <img
        src="https://vueform.com/images/logo.svg"
        alt="Vueform Logo"
        title="Vueform Logo"
        width="45"
        height="39"
        :class="classes.img"
      />
    </template>

    <!-- Default element slots -->
    <template v-for="(component, slot) in elementSlots" #[slot]><slot :name="slot" :el$="el$"><component :is="component" :el$="el$"/></slot></template>
  </ElementLayout>
</template>

<script>
  import { ref } from 'vue'
  import { defineElement } from '@vueform/vueform'

  export default defineElement({
    name: 'LogoElement',
    props: {
      color: {
        type: String,
        default: 'bw',
        required: false,
      }
    },
    setup(props, { element }) {
      const defaultClasses = ref({
        img: '',
        img_bw: 'grayscale',
        $img: (classes, { color }) => ([
          classes.img,
          color === 'bw' ? classes.img_bw : null
        ])
      })

      return {
        defaultClasses,
      }
    }
  })
</script>

<style>
/* If we were to define CSS we can put it here */
</style>

Let's register it in vueform.config.js so it can be used as type: 'logo':

js
// vueform.config.js

import { defineConfig } from '@vueform/builder'
import LogoElement from './LogoElement.vue'

export default defineConfig({
  // ...
  elements: [
    LogoElement,
  ],
})

Usage:

template
<template>
  <Vueform>
    <LogoElement name="my_logo" ... />
  </Vueform>
  <!--  or -->
  <Vueform :schema="{
    my_logo: {
      type: 'logo'
    }
  }" />
</template>

Now, we can add it to the builder:

js
// builder.config.js

import { defineConfig } from '@vueform/builder'

export default defineConfig({
  // Registering the `logo` element type
  element: {
    types: {
      logo: {
        label: 'Logo',
        description: 'Company logo',
        icon: 'https://domain.com/logo-element-icon.svg',
        category: 'static',
        rules: [],
        schema: {
          type: 'logo', // <- using our new `LogoElement` type
        },
        sections: {
          // ...
        },
        separators: {
          // ...
        },
      }
    }
  },
  // Adding `logo` to the list of available elements
  elements: [
    // ...
    'logo',
    // ...
  ]
})

As you can see we have now created a custom Vueform element and added it to the builder. When the element is dragged to the form it will appear with the default configuration defined in schema (which in our case only contains type).

The next logical step is to create a custom configuration panel for the element, as it likely requires a unique configuration that cannot be handled by the existing panels for other elements.

To learn how to do it, head to the next chapter: Custom Config Panels.

👋 Hire Vueform team for form customizations and developmentLearn more