New to Claude Skills? Learn how to install them →

vercel-labs on GitHub

Svelte JSON Renderer

OfficialFree

Transform JSON specs into Svelte components effortlessly.

Get this skill

Free · Opens the source repo

What Svelte JSON Renderer does

The Svelte JSON Renderer is a specialized tool designed for developers working with Svelte applications. It allows you to convert JSON specifications into Svelte component trees, making it easier to build UIs dynamically from structured data. This renderer is particularly useful when integrating with the @json-render/svelte library, as it simplifies the process of rendering complex component structures defined in JSON format.

By utilizing the Svelte JSON Renderer, you can create component catalogs and manage dynamic components with ease. The renderer supports defining components with specific props and behaviors, allowing for a structured approach to component development. You can register components and their actions, making it straightforward to handle events and manage state across your application.

This skill is aimed at developers who are looking to streamline their workflow when working with Svelte and JSON data. It provides a clear framework for defining components, their properties, and how they interact with user input and application state. Whether you're building a component library or rendering AI-generated specifications, this tool enhances your capability to create responsive and dynamic UIs.

In addition to component rendering, the Svelte JSON Renderer offers features like visibility conditions and dynamic props, allowing for conditional rendering based on application state. This flexibility is essential for creating interactive applications that respond to user actions and data changes effectively.

When to use it

Use this skill when you need to render Svelte components from JSON data, especially in dynamic UI scenarios.

When not to use it

This skill may not be suitable for static applications where JSON rendering is unnecessary or for projects not utilizing Svelte.

What you can build with it

Dynamic UI Rendering

Render Svelte components dynamically from JSON data, allowing for flexible and responsive UIs.

Component Catalog Management

Create and manage a catalog of reusable Svelte components defined by JSON specifications.

AI-Generated Spec Rendering

Use the renderer to display AI-generated specifications as interactive Svelte components.

How to install Svelte JSON Renderer

View source

1. Install with the skills CLI

npx skills add vercel-labs/json-render/svelte --agent claude-code

2. Or install it manually

Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.

Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs

Inside SKILL.md

Written by vercel-labs

@json-render/svelte

Svelte 5 renderer that converts json-render specs into Svelte component trees.

Quick Start

<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

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<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
}
<!-- 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>
<!-- 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

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:

ContextPurpose
StateContextShare state across components (JSON Pointer paths)
ActionContextHandle actions dispatched via the event system
VisibilityContextEnable conditional rendering based on state
ValidationContextForm field validation

Event System

Components use emit to fire named events. The element's on field maps events to action bindings:

<!-- 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>
{
  "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:

<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:

<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:

const chat = createChatUI({ api: "/api/chat-ui" });
await chat.send("Build a settings panel");

Frequently asked questions about Svelte JSON Renderer

Similar skills