vue · vercel-labs — Claude Code skill: render Vue UI from JSON specs
A skill to convert JSON specifications into Vue 3 components with data binding, visibility conditions, and action handling. Use it if you generate interfaces via AI or build UIs from JSON.
npx skills add vercel-labs/json-render --skill vue -a claude-codeWhat vue does
vue is a renderer from the @json-render/vue library that transforms JSON descriptions into live Vue 3 components. You define a catalog with component types, then a registry with their implementation using h() functions, then pass the spec to the Renderer component.
The package includes state management via StateProvider, action handling through ActionProvider, conditional rendering with VisibilityProvider, and form validation. Composables let you access state (useStateValue), bind data (useBoundProp), and check visibility (useIsVisible). JSON specs support dynamic expressions: $state to read state, $bindState for two-way binding, $cond for conditions, $template for templating.
Installation requires peer dependencies: vue ^3.5.0 and zod ^4.0.0—make sure they're already in your project.
What it helps with
- render Vue interfaces from JSON specifications
- define a component catalog with typed props and slots
- wire application state to component rendering
- add conditional rendering and form validation without boilerplate
Example prompts for the agent
- Install the vue skill and create a component catalog with types using `defineCatalog`
- Define a registry with Vue render functions and pass a spec to the `Renderer` component
- Wrap `Renderer` in `StateProvider` and `ActionProvider` to manage state and handle actions
How to install vue
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill vue -a claude-code2Manually: put the files in place
~/.claude/skills/vue/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/vue/
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill vue -a codex2Manually: put the files in place
.agents/skills/vue/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill vue -a cursor2Manually: put the files in place
.cursor/skills/vue/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install vercel-labs/json-render vue2Manually: put the files in place
.github/skills/vue/SKILL.md1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill vue -a gemini-cli2Platform CLI
gemini skills install https://github.com/vercel-labs/json-render --path skills/vueManually: put the files in place
.devin/skills/vue/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill vue -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set20
The whole set →Other versions of vue
Similar
Questions about vue
How do I install the vue skill in Claude Code?
Run npx skills add vercel-labs/json-render --skill vue -a claude-code. The skill will be available in ~/.claude/skills/vue/SKILL.md.
What does the vue skill do?
It renders Vue 3 components from JSON specs with data binding, visibility conditions, and actions. Useful for AI-generated interfaces.
What dependencies are required?
You need vue ^3.5.0 and zod ^4.0.0. Install them in your project before using the skill.
Source text
SKILL.md · Apache-2.0
---
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
```bash
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
```typescript
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
```typescript
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:
```json
{
"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
```vue
<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:
```typescript
import { createStateStore, type StateStore } from "@json-render/vue";
const store = createStateStore({ count: 0 });
```
```vue
<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
```typescript
{ "$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`:
```json
{
"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:
```typescript
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 |
© vercel-labs · Apache-2.0 · vercel-labs/json-render