vue
vercel-labs/json-render
Vue 3 renderer for building UIs from JSON specs with data binding and actions.
What is vue?
@json-render/vue converts JSON specifications into Vue component trees with full support for data binding, visibility conditions, and event handling. Use it when you need to render AI-generated UI specs, build dynamic forms, or create component-driven interfaces from JSON data.
- Render JSON specs as Vue 3 component trees with h() render functions
- Support named slots (default, header, footer, etc.) and nested content
- Bind component props to state with $state, $bindState, $cond, and $template expressions
- Handle visibility conditions and conditional rendering based on state
- Manage form validation with ValidationProvider and useFieldValidation composable
- Stream UI specs from APIs using useUIStream and useChatUI composables
How to install vue
npx skills add https://github.com/vercel-labs/json-render --skill vue- Vue 3.5.0 or higher
- npm or yarn package manager
- Zod 4.0.0 or higher for schema validation
- @json-render/core package
How to use vue
- 1.Install @json-render/vue, @json-render/core, and zod via npm
- 2.Define a catalog with defineCatalog(), specifying component props and slots using Zod schemas
- 3.Create a registry with defineRegistry(), mapping catalog components to Vue h() render functions
- 4.Wrap your component tree with StateProvider, ActionProvider, and VisibilityProvider as needed
- 5.Use the Renderer component with your spec and registry to render the JSON-based UI
- 6.Access and manipulate state using composables like useStateStore(), useStateValue(), and useBoundProp()
Use cases
- Render AI-generated UI specifications in Vue applications without manual component mapping
- Build dynamic forms with state binding, validation, and conditional field visibility
- Create reusable component catalogs with type-safe Zod schemas for prop validation
- Implement multi-step workflows or dashboards where UI structure comes from JSON data
- Stream UI updates from a backend API in real-time using the streaming composables
- Vue 3 developers building dynamic or AI-driven UIs
- Teams using JSON-based UI specifications or design systems
- Developers integrating AI-generated component trees into Vue applications
- Full-stack engineers implementing streaming UI from backend APIs
vue FAQ
Use $state, $bindState, $cond, or $template in prop definitions. For two-way binding, use { "$bindState": "/path" } on the natural value prop.
Use children for default slot content in the JSON spec. Use the slots object for named slots (header, footer, etc.) declared in the catalog.
Wrap your component tree with ValidationProvider and use the useFieldValidation(path, config) composable to access validation state for individual fields.
Yes. Create a StateStore with createStateStore() or pass your own StateStore implementation to StateProvider to integrate with Pinia, VueUse, or other state managers.
Use the useUIStream or useChatUI composables, which return Vue Refs that update as specs are streamed from your backend.
Full instructions (SKILL.md)
Source of truth, from vercel-labs/json-render.
name: vue description: Vue 3 renderer for json-render. Use when building Vue UIs from JSON specs, working with @json-render/vue, defining Vue component registries, or rendering AI-generated specs in Vue.
@json-render/vue
Vue 3 renderer that converts JSON specs into Vue component trees with data binding, visibility, and actions.
Installation
npm install @json-render/vue @json-render/core zod
Peer dependencies: vue ^3.5.0 and zod ^4.0.0.
Quick Start
Create a Catalog
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/vue/schema";
import { z } from "zod";
export const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({ title: z.string(), description: z.string().nullable() }),
slots: ["default"],
description: "A card container",
},
Layout: {
props: z.object({}),
slots: ["default", "header", "footer"],
description: "Layout with named content regions",
},
Button: {
props: z.object({ label: z.string(), action: z.string() }),
description: "A clickable button",
},
},
actions: {},
});
Define Registry with h() Render Functions
import { h } from "vue";
import { defineRegistry } from "@json-render/vue";
import { catalog } from "./catalog";
export const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) =>
h("div", { class: "card" }, [
h("h3", null, props.title),
props.description ? h("p", null, props.description) : null,
children,
]),
Layout: ({ slots }) =>
h("div", null, [
h("header", null, slots.header?.()),
h("main", null, slots.default?.()),
h("footer", null, slots.footer?.()),
]),
Button: ({ props, emit }) =>
h("button", { onClick: () => emit("press") }, props.label),
},
});
Named Slots
Use children for the "default" slot. Use the element's top-level slots object for other slot names declared by the catalog:
{
"type": "Layout",
"props": {},
"children": ["main"],
"slots": {
"header": ["heading"],
"footer": ["actions"]
}
}
Registry components receive Vue-native slot functions. Render them with slots.header?.(), slots.footer?.(), and so on. slots.default?.() renders the spec's children; the children context field is a convenience alias for that rendered result. In the JSON spec, keep default content in children rather than adding a default entry to slots.
Render Specs
<script setup lang="ts">
import { StateProvider, ActionProvider, Renderer } from "@json-render/vue";
import { registry } from "./registry";
const spec = { root: "card-1", elements: { /* ... */ } };
</script>
<template>
<StateProvider :initial-state="{ form: { name: '' } }">
<ActionProvider :handlers="{ submit: handleSubmit }">
<Renderer :spec="spec" :registry="registry" />
</ActionProvider>
</StateProvider>
</template>
Providers
| Provider | Purpose |
|---|---|
StateProvider | Share state across components (JSON Pointer paths). Accepts initialState or store for controlled mode. |
ActionProvider | Handle actions dispatched via the event system |
VisibilityProvider | Enable conditional rendering based on state |
ValidationProvider | Form field validation |
Composables
| Composable | Purpose |
|---|---|
useStateStore() | Access state context (state as ShallowRef, get, set, update) |
useStateValue(path) | Get single value from state |
useIsVisible(condition) | Check if a visibility condition is met |
useActions() | Access action context |
useAction(binding) | Get a single action dispatch function |
useFieldValidation(path, config) | Field validation state |
useBoundProp(propValue, bindingPath) | Two-way binding for $bindState/$bindItem |
Note: useStateStore().state returns a ShallowRef<StateModel> — use state.value to access.
External Store (StateStore)
Nested lists can set repeat.statePath to { "$item": "field" } inside an enclosing repeat.
Pass a StateStore to StateProvider to wire json-render to Pinia, VueUse, or any state management:
import { createStateStore, type StateStore } from "@json-render/vue";
const store = createStateStore({ count: 0 });
<StateProvider :store="store">
<Renderer :spec="spec" :registry="registry" />
</StateProvider>
Dynamic Prop Expressions
Props support $state, $bindState, $cond, $template, $computed. Use { "$bindState": "/path" } on the natural value prop for two-way binding.
Visibility Conditions
{ "$state": "/user/isAdmin" }
{ "$state": "/status", "eq": "active" }
{ "$state": "/maintenance", "not": true }
[ cond1, cond2 ] // implicit AND
Built-in Actions
setState, pushState, removeState, and validateForm are built into the Vue schema and handled by ActionProvider:
{
"action": "setState",
"params": { "statePath": "/activeTab", "value": "settings" }
}
Event System
Components use emit(event) to fire events, or on(event) for metadata (shouldPreventDefault, bound).
Streaming
useUIStream and useChatUI return Vue Refs for streaming specs from an API.
BaseComponentProps
For catalog-agnostic reusable components:
import type { BaseComponentProps } from "@json-render/vue";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) =>
h("div", null, [props.title, children]);
Key Exports
| Export | Purpose |
|---|---|
defineRegistry | Create a type-safe component registry from a catalog |
Renderer | Render a spec using a registry |
schema | Element tree schema (from @json-render/vue/schema) |
StateProvider, ActionProvider, VisibilityProvider, ValidationProvider | Context providers |
useStateStore, useStateValue, useBoundProp | State composables |
useActions, useAction | Action composables |
useFieldValidation, useIsVisible | Validation and visibility |
useUIStream, useChatUI | Streaming composables |
createStateStore | Create in-memory StateStore |
BaseComponentProps | Catalog-agnostic component props type |
Related skills
More from vercel-labs/json-render and the wider catalog.

xstate
XState Store adapter for json-render's StateStore interface. Use when integrating json-render with @xstate/store for state management via @json-render/xstate.

yaml
YAML wire format for json-render with streaming parser, prompt generation, and AI SDK transform. Use when working with @json-render/yaml, YAML-based spec streaming, yaml-spec/yaml-edit fences, or YAML prompt generation.

zustand
Zustand adapter for json-render state management integration.

codegen
Framework-agnostic code generation utilities for json-render UI trees.

konsistent-config
Enforce structural conventions and consistent exports in TypeScript codebases via konsistent.json configuration.

native-sdk
Complete toolkit for building native desktop applications with TypeScript and declarative Native markup.