Skip to main content

svelte

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.

Informations de source

Dépôt
vercel-labs/json-render
Dernière activité de la source
13 août 2026 à 19:32
Langue détectée de SKILL.md
anglais
Étoiles
18 686
Forks
992

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
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"); ```
Voir sur GitHub