Vue 3 Integration
JSON Forms provides native Vue 3 support through the @jsonforms/vue package, leveraging Vue’s Composition API with powerful composables for building reactive forms.
Installation
Install Core Dependencies
npm install @jsonforms/core @jsonforms/vue
Install Renderer Set
Choose a renderer set for your UI framework:npm install @jsonforms/vue-vuetify
Basic Usage
The <json-forms> component is the main entry point for rendering forms:
<template>
<json-forms
:data="data"
:schema="schema"
:uischema="uischema"
:renderers="renderers"
@change="onChange"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { JsonForms } from '@jsonforms/vue';
import { vuetifyRenderers } from '@jsonforms/vue-vuetify';
const data = ref({
name: '',
email: ''
});
const schema = {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' }
},
required: ['name', 'email']
};
const uischema = {
type: 'VerticalLayout',
elements: [
{ type: 'Control', scope: '#/properties/name' },
{ type: 'Control', scope: '#/properties/email' }
]
};
const renderers = Object.freeze(vuetifyRenderers);
const onChange = (event: { data: any; errors: any[] }) => {
console.log('Data:', event.data);
console.log('Errors:', event.errors);
data.value = event.data;
};
</script>
Component Props
The JsonForms component accepts the following props:
interface JsonFormsProps {
// Required
renderers: JsonFormsRendererRegistryEntry[];
// Core data
data?: any;
schema?: JsonSchema;
uischema?: UISchemaElement;
// Optional configuration
cells?: JsonFormsCellRendererRegistryEntry[];
config?: any;
readonly?: boolean;
uischemas?: JsonFormsUISchemaRegistryEntry[];
validationMode?: 'ValidateAndShow' | 'ValidateAndHide' | 'NoValidation';
ajv?: Ajv;
i18n?: JsonFormsI18nState;
additionalErrors?: ErrorObject[];
middleware?: Middleware;
}
// Emitted events
interface JsonFormsChangeEvent {
data: any;
errors: ErrorObject[];
}
If schema is not provided, JSON Forms will automatically generate one from the data. Similarly, if uischema is not provided, a default UI schema will be generated. The renderers prop is the only required prop.
Composition API
JSON Forms provides comprehensive composables for building custom renderers using Vue’s Composition API.
The primary composable for creating control renderers:
<template>
<div>
<label :for="control.id">{{ control.label }}</label>
<input
:id="control.id"
:value="control.data"
:disabled="!control.enabled"
@input="onChange($event.target.value)"
/>
<div v-if="control.errors">{{ control.errors }}</div>
</div>
</template>
<script setup lang="ts">
import { useJsonFormsControl } from '@jsonforms/vue';
import { ControlElement } from '@jsonforms/core';
const props = defineProps<{
schema: any;
uischema: ControlElement;
path: string;
}>();
const { control, handleChange } = useJsonFormsControl(props);
const onChange = (value: string) => {
handleChange(props.path, value);
};
</script>
Control Composables
Basic Controls
Array Controls
Combinators
// Generic control
const { control, handleChange } = useJsonFormsControl(props);
// Enum/select control
const { control, handleChange } = useJsonFormsEnumControl(props);
// OneOf enum control
const { control, handleChange } = useJsonFormsOneOfEnumControl(props);
// Multi-select control
const { control, handleChange } = useJsonFormsMultiEnumControl(props);
// Control with detail (objects/arrays)
const { control, handleChange } = useJsonFormsControlWithDetail(props);
const {
control,
handleChange,
addItem,
removeItems,
moveUp,
moveDown
} = useJsonFormsArrayControl(props);
// Usage
addItem(path, value)();
removeItems(path, [index])();
moveUp(path, index)();
moveDown(path, index)();
// AllOf combinator
const { control, handleChange } = useJsonFormsAllOfControl(props);
// AnyOf combinator
const { control, handleChange } = useJsonFormsAnyOfControl(props);
// OneOf combinator
const { control, handleChange } = useJsonFormsOneOfControl(props);
Control Properties
The control reactive ref provides access to all control state:
interface ControlProps {
// Identification
id: string;
path: string;
// Data
data: any;
// Schema
schema: JsonSchema;
uischema: ControlElement;
rootSchema: JsonSchema;
// State
enabled: boolean;
visible: boolean;
required: boolean;
// Validation
errors: string;
// Metadata
label: string;
description: string;
// Configuration
config: any;
cells: JsonFormsCellRendererRegistryEntry[];
renderers: JsonFormsRendererRegistryEntry[];
}
For layout renderers (Vertical, Horizontal, Group):
<template>
<div class="vertical-layout">
<dispatch-renderer
v-for="(element, index) in layout.uischema.elements"
:key="index"
:schema="layout.schema"
:uischema="element"
:path="layout.path"
:enabled="layout.enabled"
:renderers="layout.renderers"
:cells="layout.cells"
/>
</div>
</template>
<script setup lang="ts">
import { useJsonFormsLayout } from '@jsonforms/vue';
import { Layout } from '@jsonforms/core';
import { DispatchRenderer } from '@jsonforms/vue';
const props = defineProps<{
schema: any;
uischema: Layout;
path: string;
}>();
const { layout } = useJsonFormsLayout(props);
</script>
For rendering arrays as layouts:
<template>
<div>
<h3>{{ layout.label }}</h3>
<div v-for="(item, index) in layout.data" :key="index">
<dispatch-renderer
:schema="layout.schema"
:uischema="layout.uischema"
:path="getChildPath(index)"
/>
<button @click="removeItems(layout.path, [index])()">Remove</button>
</div>
<button @click="addItem(layout.path, createDefaultValue())()">Add</button>
</div>
</template>
<script setup lang="ts">
import { useJsonFormsArrayLayout } from '@jsonforms/vue';
import { ControlElement } from '@jsonforms/core';
const props = defineProps<{
schema: any;
uischema: ControlElement;
path: string;
}>();
const { layout, addItem, removeItems } = useJsonFormsArrayLayout(props);
const getChildPath = (index: number) => `${props.path}/${index}`;
const createDefaultValue = () => ({});
</script>
Cell Composables
For rendering cells in tables:
// Generic cell
const { cell, handleChange } = useJsonFormsCell(props);
// Enum cell
const { cell, handleChange } = useJsonFormsEnumCell(props);
// OneOf enum cell
const { cell, handleChange } = useJsonFormsOneOfEnumCell(props);
// Dispatch cell (for dynamic cell rendering)
const { cell, handleChange } = useJsonFormsDispatchCell(props);
Other Composables
// Generic renderer (for meta renderers like DispatchRenderer)
const { renderer, rootSchema } = useJsonFormsRenderer(props);
// Label renderer
const { label } = useJsonFormsLabel(props);
// Master list item (for master-detail patterns)
const { item } = useJsonFormsMasterListItem(props);
// Categorization
const { layout, categories } = useJsonFormsCategorization(props);
Renderer Props Helper
Use the rendererProps helper to declare props for custom renderers:
<script setup lang="ts">
import { rendererProps, useJsonFormsControl } from '@jsonforms/vue';
import { ControlElement } from '@jsonforms/core';
// Generic renderer props
const props = defineProps({
...rendererProps<ControlElement>()
});
const { control, handleChange } = useJsonFormsControl(props);
</script>
Master List Item Props
For master-detail list items:
<script setup lang="ts">
import { masterListItemProps, useJsonFormsMasterListItem } from '@jsonforms/vue';
const props = defineProps({
...masterListItemProps()
});
const { item } = useJsonFormsMasterListItem(props);
</script>
Provide/Inject Pattern
JSON Forms uses Vue’s provide/inject for dependency injection:
interface InjectJsonFormsState {
jsonforms: JsonFormsSubStates;
}
interface InjectJsonFormsDispatch {
dispatch: Dispatch<CoreActions>;
}
The JsonForms component provides these values automatically. Custom renderers receive them via the composables.
Advanced Features
Middleware
Intercept and transform actions:
<template>
<json-forms
:data="data"
:schema="schema"
:renderers="renderers"
:middleware="loggingMiddleware"
@change="onChange"
/>
</template>
<script setup lang="ts">
import type { Middleware } from '@jsonforms/core';
const loggingMiddleware: Middleware = (state, action, reducer) => {
console.log('Action:', action.type);
console.log('Before:', state);
const newState = reducer(state, action);
console.log('After:', newState);
return newState;
};
</script>
Custom Validation
<template>
<json-forms
:data="data"
:schema="schema"
:renderers="renderers"
:ajv="customAjv"
@change="onChange"
/>
</template>
<script setup lang="ts">
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const customAjv = new Ajv({
allErrors: true,
verbose: true,
strict: false
});
addFormats(customAjv);
// Add custom formats
customAjv.addFormat('postal-code', /^\d{5}$/);
</script>
Internationalization
<template>
<json-forms
:data="data"
:schema="schema"
:renderers="renderers"
:i18n="i18nConfig"
@change="onChange"
/>
</template>
<script setup lang="ts">
import type { Translator, JsonFormsI18nState } from '@jsonforms/core';
const translations = {
'en': {
'name': 'Name',
'email': 'Email Address'
},
'de': {
'name': 'Name',
'email': 'E-Mail-Adresse'
}
};
const locale = ref('en');
const translator: Translator = (key: string, defaultMessage: string) => {
return translations[locale.value]?.[key] || defaultMessage;
};
const i18nConfig: JsonFormsI18nState = {
locale: locale.value,
translate: translator,
translateError: (error) => error.message
};
</script>
Readonly Mode
<template>
<div>
<button @click="readonly = !readonly">Toggle Readonly</button>
<json-forms
:data="data"
:schema="schema"
:renderers="renderers"
:readonly="readonly"
@change="onChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const readonly = ref(false);
</script>
Validation Modes
<template>
<div>
<select v-model="validationMode">
<option value="ValidateAndShow">Validate and Show</option>
<option value="ValidateAndHide">Validate and Hide</option>
<option value="NoValidation">No Validation</option>
</select>
<json-forms
:data="data"
:schema="schema"
:renderers="renderers"
:validation-mode="validationMode"
@change="onChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import type { ValidationMode } from '@jsonforms/core';
const validationMode = ref<ValidationMode>('ValidateAndShow');
</script>
Reactivity Best Practices
Freezing Renderers
For optimal performance, freeze renderer arrays:
import { vuetifyRenderers } from '@jsonforms/vue-vuetify';
const renderers = Object.freeze(vuetifyRenderers);
Readonly Arrays
Use the MaybeReadonly type for renderer/cell props:
import type { MaybeReadonly } from '@jsonforms/vue';
import type { JsonFormsRendererRegistryEntry } from '@jsonforms/core';
const renderers: MaybeReadonly<JsonFormsRendererRegistryEntry[]> =
Object.freeze(vuetifyRenderers);
Computed Properties
Use computed properties for derived state:
<script setup lang="ts">
import { computed } from 'vue';
import { useJsonFormsControl } from '@jsonforms/vue';
const { control } = useJsonFormsControl(props);
const hasError = computed(() => !!control.value.errors);
const isDisabled = computed(() => !control.value.enabled);
</script>
TypeScript Support
JSON Forms Vue is written in TypeScript and exports all types:
import type {
RendererProps,
ControlProps,
LayoutProps,
JsonFormsChangeEvent,
MaybeReadonly,
InjectJsonFormsState,
InjectJsonFormsDispatch
} from '@jsonforms/vue';
import type {
JsonSchema,
UISchemaElement,
ControlElement,
Layout,
ValidationMode
} from '@jsonforms/core';
Component Lifecycle
The JsonForms component:
- Initializes core state on mount
- Generates missing schema/uischema if not provided
- Watches all props for changes and updates state reactively
- Emits
change events on mount and whenever data/errors change
- Provides
jsonforms state and dispatch to child components
The change event is emitted immediately on mount with the initial data and validation errors. This allows parent components to react to default values and initial validation.
Next Steps