
svelte
★ 15,600by vercel · part of vercel-labs/json-render
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,…
This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.
by vercel
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,…
npx skills add https://github.com/vercel-labs/json-render --skill svelte
Download ZIPGitHub15.6k
@json-render/svelte
Svelte 5 renderer that converts json-render specs into Svelte component trees.
Creating a Catalog
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>:
interface BaseComponentProps {
props: TProps; // Resolved props for this component
children?: Snippet; // Child elements (use {@render children()})
emit: (event: string) => void; // Fire a named event
bindings?: Record ; // Map of prop names to state paths (for $bindState)
loading?: boolean; // True while spec is streaming
}
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps {}
let { props, emit }: Props = $props();
emit("press")}>
{props.label}
import type { Snippet } from "svelte";
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps {
children?: Snippet;
}
let { props, children }: Props = $props();
## {props.title}
{#if children}
{@render children()}
{/if}
Creating a Registry
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:
{
"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:
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps {}
let { props, emit }: Props = $props();
emit("press")}>{props.label}
{
"type": "Button",
"props": { "label": "Submit" },
"on": { "press": { "action": "submit" } }
}
Built-in Actions
The setState action is handled automatically and updates the state model:
{
"action": "setState",
"actionParams": { "statePath": "/activeTab", "value": "home" }
}
Other built-in actions: pushState, removeState, push, pop.
Dynamic Props and Two-Way Binding
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:
import { getBoundProp } from "@json-render/svelte";
import type { BaseComponentProps } from "@json-render/svelte";
interface Props extends BaseComponentProps {}
let { props, bindings }: Props = $props();
let value = getBoundProp (
() => props.value,
() => bindings?.value,
);
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:
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");
}
{stream.isStreaming ? "Generating..." : "Generate UI"}
{#if stream.spec}
{/if}
Use createChatUI for chat + UI responses:
const chat = createChatUI({ api: "/api/chat-ui" });
await chat.send("Build a settings panel");
npx skills add https://github.com/vercel-labs/json-render --skill svelteRun this in your project — your agent picks the skill up automatically.
Quick Start
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 };
No common issues documented yet. If you hit a problem, the repository's GitHub Issues page is the best place to look.