PluginBench
Skill
Official
Pass
Audit score 90

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
Prerequisites
  • Vue 3.5.0 or higher
  • npm or yarn package manager
  • Zod 4.0.0 or higher for schema validation
  • @json-render/core package
Claude Code
Cursor
Windsurf
Cline

How to use vue

  1. 1.Install @json-render/vue, @json-render/core, and zod via npm
  2. 2.Define a catalog with defineCatalog(), specifying component props and slots using Zod schemas
  3. 3.Create a registry with defineRegistry(), mapping catalog components to Vue h() render functions
  4. 4.Wrap your component tree with StateProvider, ActionProvider, and VisibilityProvider as needed
  5. 5.Use the Renderer component with your spec and registry to render the JSON-based UI
  6. 6.Access and manipulate state using composables like useStateStore(), useStateValue(), and useBoundProp()

Use cases

Good for
  • 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
Who it's for
  • 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

How do I bind component props to state?

Use $state, $bindState, $cond, or $template in prop definitions. For two-way binding, use { "$bindState": "/path" } on the natural value prop.

What's the difference between children and slots?

Use children for default slot content in the JSON spec. Use the slots object for named slots (header, footer, etc.) declared in the catalog.

How do I handle form validation?

Wrap your component tree with ValidationProvider and use the useFieldValidation(path, config) composable to access validation state for individual fields.

Can I use an external state management library like Pinia?

Yes. Create a StateStore with createStateStore() or pass your own StateStore implementation to StateProvider to integrate with Pinia, VueUse, or other state managers.

How do I stream UI specs from an API?

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

ProviderPurpose
StateProviderShare state across components (JSON Pointer paths). Accepts initialState or store for controlled mode.
ActionProviderHandle actions dispatched via the event system
VisibilityProviderEnable conditional rendering based on state
ValidationProviderForm field validation

Composables

ComposablePurpose
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

ExportPurpose
defineRegistryCreate a type-safe component registry from a catalog
RendererRender a spec using a registry
schemaElement tree schema (from @json-render/vue/schema)
StateProvider, ActionProvider, VisibilityProvider, ValidationProviderContext providers
useStateStore, useStateValue, useBoundPropState composables
useActions, useActionAction composables
useFieldValidation, useIsVisibleValidation and visibility
useUIStream, useChatUIStreaming composables
createStateStoreCreate in-memory StateStore
BaseComponentPropsCatalog-agnostic component props type