svelte — Claude Code skill: JSON UI rendering in Svelte
Guides AI coding agents through building Svelte 5 component trees from json-render specs, including state, events, bindings, and catalogs.
npx skills add vercel-labs/json-render --skill svelte -a claude-codeWhat svelte does
This skill documents the @json-render/svelte renderer, which turns a JSON spec into a Svelte component tree. It shows how to pass a spec and component registry to Renderer inside JsonUIProvider. Use it when the UI structure is delivered as JSON.
It covers component catalogs with defineCatalog and zod, plus registry setup with defineRegistry. Components can receive props, children, emit, bindings, and a streaming flag. An event emitted by a component is connected to an action through the element's on field.
The skill also covers visible conditions, setState, pushState, removeState, push, and pop, along with two-way bindings via getBoundProp. createUIStream handles streamed specs. You need Svelte 5 and @json-render/svelte, so check the project version before applying its code.
What it helps with
- render a Svelte component tree from a JSON spec
- define a component catalog with
defineCatalog - map a component event to an action in
on - bind a form field to a state path
- show or hide an element from state
Example prompts for the agent
- "Build a Svelte 5 UI from a JSON spec with `@json-render/svelte`."
- "Create a `Button` and `Card` catalog using `defineCatalog` and `zod`."
- "Add a `getBoundProp` binding for a form field in this Svelte component."
How to install svelte
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill svelte -a claude-code2Manually: put the files in place
~/.claude/skills/svelte/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/svelte/
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill svelte -a codex2Manually: put the files in place
.agents/skills/svelte/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill svelte -a cursor2Manually: put the files in place
.cursor/skills/svelte/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install vercel-labs/json-render svelte2Manually: put the files in place
.github/skills/svelte/SKILL.md1Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill svelte -a gemini-cli2Platform CLI
gemini skills install https://github.com/vercel-labs/json-render --path skills/svelteManually: put the files in place
.devin/skills/svelte/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add vercel-labs/json-render --skill svelte -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set20
The whole set →Similar
Questions about svelte
How do I install svelte in Claude Code?
Run npx skills add vercel-labs/json-render --skill svelte -a claude-code, then run npx skills add vercel-labs/json-render --skill svelte -a claude-code again. The skill file is at ~/.claude/skills/svelte/SKILL.md.
What does svelte do?
The skill explains @json-render/svelte, which renders json-render specifications as Svelte 5 component trees. It covers catalogs, registries, state, actions, and events.
How are component events connected to actions?
A component calls emit with an event name, and the JSON element's on field maps that event to an action. Action handlers can be registered with defineRegistry.
Source text
SKILL.md · Apache-2.0
---
name: svelte
description: Svelte 5 renderer for json-render that turns JSON specs into Svelte component trees. Use when working with @json-render/svelte, building Svelte UIs from JSON, creating component catalogs, or rendering AI-generated specs.
---
# @json-render/svelte
Svelte 5 renderer that converts json-render specs into Svelte component trees.
## Quick Start
```svelte
<script lang="ts">
import { Renderer, JsonUIProvider } from "@json-render/svelte";
import type { Spec } from "@json-render/svelte";
import Card from "./components/Card.svelte";
import Button from "./components/Button.svelte";
interface Props {
spec: Spec | null;
}
let { spec }: Props = $props();
const registry = { Card, Button };
</script>
<JsonUIProvider>
<Renderer {spec} {registry} />
</JsonUIProvider>
```
## Creating a Catalog
```typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/svelte";
import { z } from "zod";
export const catalog = defineCatalog(schema, {
components: {
Button: {
props: z.object({
label: z.string(),
variant: z.enum(["primary", "secondary"]).nullable(),
}),
description: "Clickable button",
},
Card: {
props: z.object({ title: z.string() }),
description: "Card container with title",
},
},
});
```
## Defining Components
Components should accept `BaseComponentProps<TProps>`:
```typescript
interface BaseComponentProps<TProps> {
props: TProps; // Resolved props for this component
children?: Snippet; // Child elements (use {@render children()})
emit: (event: string) => void; // Fire a named event
bindings?: Record<string, string>; // Map of prop names to state paths (for $bindState)
loading?: boolean; // True while spec is streaming
}
```
```svelte
<!-- Button.svelte -->
<script lang="ts">
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps<{ label: string; variant?: string }> {}
let { props, emit }: Props = $props();
</script>
<button class={props.variant} onclick={() => emit("press")}>
{props.label}
</button>
```
```svelte
<!-- Card.svelte -->
<script lang="ts">
import type { Snippet } from "svelte";
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps<{ title: string }> {
children?: Snippet;
}
let { props, children }: Props = $props();
</script>
<div class="card">
<h2>{props.title}</h2>
{#if children}
{@render children()}
{/if}
</div>
```
## Creating a Registry
```typescript
import { defineRegistry } from "@json-render/svelte";
import { catalog } from "./catalog";
import Card from "./components/Card.svelte";
import Button from "./components/Button.svelte";
const { registry, handlers, executeAction } = defineRegistry(catalog, {
components: {
Card,
Button,
},
actions: {
submit: async (params, setState, state) => {
// handle action
},
},
});
```
## Spec Structure (Element Tree)
The Svelte schema uses the element tree format:
```json
{
"root": "card1",
"elements": {
"card1": {
"type": "Card",
"props": { "title": "Hello" },
"children": ["btn1"]
},
"btn1": {
"type": "Button",
"props": { "label": "Click me" }
}
}
}
```
## Visibility Conditions
Use `visible` on elements to show/hide based on state:
- `{ "$state": "/path" }` - truthy check
- `{ "$state": "/path", "eq": value }` - equality check
- `{ "$state": "/path", "not": true }` - falsy check
- `{ "$and": [cond1, cond2] }` - AND conditions
- `{ "$or": [cond1, cond2] }` - OR conditions
## Providers (via JsonUIProvider)
`JsonUIProvider` composes all contexts. Individual contexts:
| Context | Purpose |
| ------------------- | -------------------------------------------------- |
| `StateContext` | Share state across components (JSON Pointer paths) |
| `ActionContext` | Handle actions dispatched via the event system |
| `VisibilityContext` | Enable conditional rendering based on state |
| `ValidationContext` | Form field validation |
## Event System
Components use `emit` to fire named events. The element's `on` field maps events to action bindings:
```svelte
<!-- Button.svelte -->
<script lang="ts">
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps<{ label: string }> {}
let { props, emit }: Props = $props();
</script>
<button onclick={() => emit("press")}>{props.label}</button>
```
```json
{
"type": "Button",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } }
}
```
## Built-in Actions
The `setState` action is handled automatically and updates the state model:
```json
{
"action": "setState",
"actionParams": { "statePath": "/activeTab", "value": "home" }
}
```
Other built-in actions: `pushState`, `removeState`, `push`, `pop`.
## Dynamic Props and Two-Way Binding
Nested lists can set `repeat.statePath` to `{ "$item": "field" }` inside an enclosing repeat.
Expression forms resolved before your component receives props:
- `{"$state": "/state/key"}` - read from state
- `{"$bindState": "/form/email"}` - read + write-back to state
- `{"$bindItem": "field"}` - read + write-back for repeat items
- `{"$cond": <condition>, "$then": <value>, "$else": <value>}` - conditional value
For writable bindings inside components, use `getBoundProp`:
```svelte
<script lang="ts">
import { getBoundProp } from "@json-render/svelte";
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps<{ value?: string }> {}
let { props, bindings }: Props = $props();
let value = getBoundProp<string>(
() => props.value,
() => bindings?.value,
);
</script>
<input bind:value={value.current} />
```
## Context Helpers
Preferred helpers:
- `getStateValue(path)` - returns `{ current }` (read/write)
- `getBoundProp(() => value, () => bindingPath)` - returns `{ current }` (read/write when bound)
- `isVisible(condition)` - returns `{ current }` (boolean)
- `getAction(name)` - returns `{ current }` (registered handler)
Advanced context access:
- `getStateContext()`
- `getActionContext()`
- `getVisibilityContext()`
- `getValidationContext()`
- `getOptionalValidationContext()`
- `getFieldValidation(ctx, path, config?)`
## Streaming UI
Use `createUIStream` for spec streaming:
```svelte
<script lang="ts">
import { createUIStream, Renderer } from "@json-render/svelte";
const stream = createUIStream({
api: "/api/generate-ui",
onComplete: (spec) => console.log("Done", spec),
});
async function generate() {
await stream.send("Create a login form");
}
</script>
<button onclick={generate} disabled={stream.isStreaming}>
{stream.isStreaming ? "Generating..." : "Generate UI"}
</button>
{#if stream.spec}
<Renderer spec={stream.spec} {registry} loading={stream.isStreaming} />
{/if}
```
Use `createChatUI` for chat + UI responses:
```typescript
const chat = createChatUI({ api: "/api/chat-ui" });
await chat.send("Build a settings panel");
```
© vercel-labs · Apache-2.0 · vercel-labs/json-render