Skip to content

Preview Adapters

The preview system renders your form in real-time as you build it. Different renderer sets (adapters) can be used depending on which UI framework your application targets.

Architecture

A PreviewAdapter bundles everything needed to render a JSON Forms instance:

ts
interface PreviewAdapter {
  /** Unique identifier for the adapter */
  id: string
  /** Human-readable name */
  name: string
  /** JSON Forms renderer registry entries */
  renderers: JsonFormsRendererRegistryEntry[]
  /** Optional cell renderers for table-like layouts */
  cells?: JsonFormsCellRendererRegistryEntry[]
  /** Optional setup function called on the Vue app instance */
  setup?: (app: App) => void
  /** Optional CSS string to inject into the preview */
  styles?: string
}

The renderers array tells JSON Forms which Vue components to use for each control type and layout. The optional setup function is called on the preview's Vue app instance, allowing you to install plugins like Vuetify. The styles string provides CSS that is injected into the preview panel.

Using the Vanilla Adapter

The vanilla adapter uses @jsonforms/vue-vanilla, which renders forms with plain HTML elements and no external UI framework. This is the default adapter.

bash
pnpm add @jsonforms/core @jsonforms/vue @jsonforms/vue-vanilla
ts
import { vanillaAdapter } from '@banclo/jsonforms-preview'

// Pass to FormBuilder
<FormBuilder :preview-adapter="vanillaAdapter.id" />

The vanilla adapter includes built-in CSS styles that provide clean, unstyled form controls suitable for customization with your own CSS or Tailwind classes.

Using the Vuetify Adapter

The Vuetify adapter uses @jsonforms/vue-vuetify for Material Design styled forms. Because Vuetify is a large dependency, the adapter is loaded lazily.

bash
pnpm add @jsonforms/core @jsonforms/vue @jsonforms/vue-vuetify vuetify @mdi/font
ts
// Dynamic import (recommended)
const { createVuetifyAdapter } = await import('@banclo/jsonforms-preview/adapters/vuetify-adapter')

// The adapter is created asynchronously
const vuetifyAdapter = await createVuetifyAdapter()

The createVuetifyAdapter() function dynamically imports both @jsonforms/vue-vuetify and vuetify, creates a Vuetify instance, and returns an adapter that installs Vuetify on the preview app and imports the required CSS.

Merging Renderers

The mergeRenderers utility from @banclo/jsonforms-preview lets you combine an adapter's renderers with your own custom renderers. This is useful when you want to extend an existing adapter with additional controls without replacing its built-in renderers.

ts
import { mergeRenderers } from '@banclo/jsonforms-preview'
import { rankWith, formatIs } from '@jsonforms/core'
import StarRatingRenderer from './StarRatingRenderer.vue'

const { createVuetifyAdapter } = await import('@banclo/jsonforms-preview/adapters/vuetify-adapter')
const vuetifyAdapter = await createVuetifyAdapter()

const combinedRenderers = mergeRenderers(vuetifyAdapter.renderers, [
  {
    renderer: StarRatingRenderer,
    tester: rankWith(10, formatIs('rating')),
  },
])

const adapter = {
  ...vuetifyAdapter,
  renderers: combinedRenderers,
}

mergeRenderers accepts two or more renderer arrays and returns a single merged array. When renderers target the same control, the last array's entries take precedence.

Creating a Custom Adapter

You can create your own adapter to support any renderer set. Here is an example for a hypothetical custom design system:

ts
import type { PreviewAdapter } from '@banclo/jsonforms-preview'
import { myRenderers, myCells } from './my-renderers'
import { createMyDesignSystem } from './my-design-system'

export function createCustomAdapter(): PreviewAdapter {
  const designSystem = createMyDesignSystem()

  return {
    id: 'my-design-system',
    name: 'My Design System',
    renderers: myRenderers,
    cells: myCells,
    setup: (app) => {
      app.use(designSystem)
    },
    styles: `
      @import 'my-design-system/dist/styles.css';

      .mds-form-control {
        margin-bottom: 1rem;
      }
      .mds-form-label {
        font-weight: 600;
        margin-bottom: 0.25rem;
      }
    `,
  }
}

Adapter Requirements

Your adapter must provide at minimum:

  1. id -- a unique string identifier.
  2. name -- displayed in the UI when switching between adapters.
  3. renderers -- an array of JSON Forms renderer registry entries. Each entry maps a tester function to a Vue component. See the JSON Forms documentation for how to create renderers.

Registering Custom Renderers

If your adapter needs to support custom controls (like the star rating example from Custom Components), add renderer entries for those controls:

ts
import { rankWith, formatIs } from '@jsonforms/core'
import StarRatingRenderer from './StarRatingRenderer.vue'

const customRenderers = [
  ...vanillaRenderers,
  {
    renderer: StarRatingRenderer,
    tester: rankWith(10, formatIs('rating')),
  },
]

export const customAdapter: PreviewAdapter = {
  id: 'custom',
  name: 'Custom',
  renderers: customRenderers,
}

The rankWith function assigns a priority to your custom renderer. Higher values take precedence, so a rank of 10 ensures your rating renderer is used instead of the default integer renderer when the format option is "rating".

Switching Adapters

The FormBuilder component accepts a previewAdapter prop that determines which adapter is used for the live preview:

vue
<template>
  <FormBuilder preview-adapter="vanilla" />
</template>

You can also switch adapters at runtime by changing the prop value. The preview panel re-renders with the new adapter's renderers and styles.