Labsco
vercel-labs logo

svelte

15,600

by 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,…

🔥🔥🔥✓ VerifiedFreeQuick setup
🧰 Not standalone. This skill ships with vercel-labs/json-render and only works together with that tool — install the tool first, then add this skill.

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");