{"id":41215773,"url":"https://github.com/vercel-labs/json-render","last_synced_at":"2026-04-02T16:10:16.826Z","repository":{"id":332678862,"uuid":"1134411737","full_name":"vercel-labs/json-render","owner":"vercel-labs","description":"The Generative UI framework","archived":false,"fork":false,"pushed_at":"2026-03-28T01:22:05.000Z","size":4591,"stargazers_count":13562,"open_issues_count":73,"forks_count":728,"subscribers_count":42,"default_branch":"main","last_synced_at":"2026-03-28T05:48:54.787Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://json-render.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vercel-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-14T17:22:39.000Z","updated_at":"2026-03-28T05:41:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vercel-labs/json-render","commit_stats":null,"previous_names":["vercel-labs/json-render"],"tags_count":246,"template":false,"template_full_name":null,"purl":"pkg:github/vercel-labs/json-render","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel-labs%2Fjson-render","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel-labs%2Fjson-render/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel-labs%2Fjson-render/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel-labs%2Fjson-render/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vercel-labs","download_url":"https://codeload.github.com/vercel-labs/json-render/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel-labs%2Fjson-render/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31309671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-23T00:01:14.264Z","updated_at":"2026-04-02T16:10:16.795Z","avatar_url":"https://github.com/vercel-labs.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# json-render\n\n**The Generative UI framework.**\n\nGenerate dynamic, personalized UIs from prompts without sacrificing reliability. Predefined components and actions for safe, predictable output.\n\n```bash\n# for React\nnpm install @json-render/core @json-render/react\n# for React with pre-built shadcn/ui components\nnpm install @json-render/shadcn\n# or for React Native\nnpm install @json-render/core @json-render/react-native\n# or for video\nnpm install @json-render/core @json-render/remotion\n# or for PDF documents\nnpm install @json-render/core @json-render/react-pdf\n# or for HTML email\nnpm install @json-render/core @json-render/react-email @react-email/components @react-email/render\n# or for Vue\nnpm install @json-render/core @json-render/vue\n# or for Svelte\nnpm install @json-render/core @json-render/svelte\n# or for SolidJS\nnpm install @json-render/core @json-render/solid\n# or for terminal UIs\nnpm install @json-render/core @json-render/ink ink react\n# or for full Next.js apps (routes, layouts, SSR, metadata)\nnpm install @json-render/core @json-render/react @json-render/next\n# or for 3D scenes\nnpm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three\n```\n\n## Why json-render?\n\njson-render is a **Generative UI** framework: AI generates interfaces from natural language prompts, constrained to components you define. You set the guardrails, AI generates within them:\n\n- **Guardrailed** - AI can only use components in your catalog\n- **Predictable** - JSON output matches your schema, every time\n- **Fast** - Stream and render progressively as the model responds\n- **Cross-Platform** - React, Vue, Svelte, Solid (web), React Native (mobile) from the same catalog\n- **Batteries Included** - 36 pre-built shadcn/ui components ready to use\n\n## Quick Start\n\n### 1. Define Your Catalog\n\n```typescript\nimport { defineCatalog } from \"@json-render/core\";\nimport { schema } from \"@json-render/react/schema\";\nimport { z } from \"zod\";\n\nconst catalog = defineCatalog(schema, {\n  components: {\n    Card: {\n      props: z.object({ title: z.string() }),\n      description: \"A card container\",\n    },\n    Metric: {\n      props: z.object({\n        label: z.string(),\n        value: z.string(),\n        format: z.enum([\"currency\", \"percent\", \"number\"]).nullable(),\n      }),\n      description: \"Display a metric value\",\n    },\n    Button: {\n      props: z.object({\n        label: z.string(),\n        action: z.string(),\n      }),\n      description: \"Clickable button\",\n    },\n  },\n  actions: {\n    export_report: { description: \"Export dashboard to PDF\" },\n    refresh_data: { description: \"Refresh all metrics\" },\n  },\n});\n```\n\n### 2. Define Your Components\n\n```tsx\nimport { defineRegistry, Renderer } from \"@json-render/react\";\n\nconst { registry } = defineRegistry(catalog, {\n  components: {\n    Card: ({ props, children }) =\u003e (\n      \u003cdiv className=\"card\"\u003e\n        \u003ch3\u003e{props.title}\u003c/h3\u003e\n        {children}\n      \u003c/div\u003e\n    ),\n    Metric: ({ props }) =\u003e (\n      \u003cdiv className=\"metric\"\u003e\n        \u003cspan\u003e{props.label}\u003c/span\u003e\n        \u003cspan\u003e{format(props.value, props.format)}\u003c/span\u003e\n      \u003c/div\u003e\n    ),\n    Button: ({ props, emit }) =\u003e (\n      \u003cbutton onClick={() =\u003e emit(\"press\")}\u003e{props.label}\u003c/button\u003e\n    ),\n  },\n});\n```\n\n### 3. Render AI-Generated Specs\n\n```tsx\nfunction Dashboard({ spec }) {\n  return \u003cRenderer spec={spec} registry={registry} /\u003e;\n}\n```\n\n**That's it.** AI generates JSON, you render it safely.\n\n---\n\n## Packages\n\n| Package                     | Description                                                            |\n| --------------------------- | ---------------------------------------------------------------------- |\n| `@json-render/core`         | Schemas, catalogs, AI prompts, dynamic props, SpecStream utilities     |\n| `@json-render/react`        | React renderer, contexts, hooks                                        |\n| `@json-render/vue`          | Vue 3 renderer, composables, providers                                 |\n| `@json-render/svelte`       | Svelte 5 renderer with runes-based reactivity                          |\n| `@json-render/solid`        | SolidJS renderer with fine-grained reactive contexts                   |\n| `@json-render/shadcn`       | 36 pre-built shadcn/ui components (Radix UI + Tailwind CSS)            |\n| `@json-render/shadcn-svelte`| 36 pre-built shadcn-svelte components (Svelte 5 + Tailwind CSS)        |\n| `@json-render/react-three-fiber` | React Three Fiber renderer for 3D scenes (19 built-in components)  |\n| `@json-render/react-native` | React Native renderer with standard mobile components                  |\n| `@json-render/next`         | Next.js renderer — JSON becomes full apps with routes, layouts, SSR    |\n| `@json-render/remotion`     | Remotion video renderer, timeline schema                               |\n| `@json-render/react-pdf`    | React PDF renderer for generating PDF documents from specs             |\n| `@json-render/react-email`  | React Email renderer for HTML/plain-text emails from specs             |\n| `@json-render/ink`          | Ink terminal renderer with built-in components for interactive TUIs.   |\n| `@json-render/image`        | Image renderer for SVG/PNG output (OG images, social cards) via Satori |\n| `@json-render/codegen`      | Utilities for generating code from json-render UI trees                |\n| `@json-render/redux`        | Redux / Redux Toolkit adapter for `StateStore`                         |\n| `@json-render/zustand`      | Zustand adapter for `StateStore`                                       |\n| `@json-render/jotai`        | Jotai adapter for `StateStore`                                         |\n| `@json-render/xstate`       | XState Store (atom) adapter for `StateStore`                           |\n| `@json-render/mcp`          | MCP Apps integration for Claude, ChatGPT, Cursor, VS Code              |\n| `@json-render/yaml`         | YAML wire format with streaming parser, edit modes, AI SDK transform   |\n\n## Renderers\n\n### React (UI)\n\n```tsx\nimport { defineRegistry, Renderer } from \"@json-render/react\";\nimport { schema } from \"@json-render/react/schema\";\n\n// Flat spec format (root key + elements map)\nconst spec = {\n  root: \"card-1\",\n  elements: {\n    \"card-1\": {\n      type: \"Card\",\n      props: { title: \"Hello\" },\n      children: [\"button-1\"],\n    },\n    \"button-1\": {\n      type: \"Button\",\n      props: { label: \"Click me\" },\n      children: [],\n    },\n  },\n};\n\n// defineRegistry creates a type-safe component registry\nconst { registry } = defineRegistry(catalog, { components });\n\u003cRenderer spec={spec} registry={registry} /\u003e;\n```\n\n### Vue (UI)\n\n```typescript\nimport { h } from \"vue\";\nimport { defineRegistry, Renderer } from \"@json-render/vue\";\nimport { schema } from \"@json-render/vue/schema\";\n\nconst { registry } = defineRegistry(catalog, {\n  components: {\n    Card: ({ props, children }) =\u003e\n      h(\"div\", { class: \"card\" }, [h(\"h3\", null, props.title), children]),\n    Button: ({ props, emit }) =\u003e\n      h(\"button\", { onClick: () =\u003e emit(\"press\") }, props.label),\n  },\n});\n\n// In your Vue component template:\n// \u003cRenderer :spec=\"spec\" :registry=\"registry\" /\u003e\n```\n\n### Svelte (UI)\n\n```typescript\nimport { defineRegistry, Renderer } from \"@json-render/svelte\";\nimport { schema } from \"@json-render/svelte/schema\";\n\nconst { registry } = defineRegistry(catalog, {\n  components: {\n    Card: ({ props, children }) =\u003e /* Svelte 5 snippet */,\n    Button: ({ props, emit }) =\u003e /* Svelte 5 snippet */,\n  },\n});\n\n// In your Svelte component:\n// \u003cRenderer spec={spec} registry={registry} /\u003e\n```\n\n### Solid (UI)\n\n```tsx\nimport { defineRegistry, Renderer } from \"@json-render/solid\";\nimport { schema } from \"@json-render/solid/schema\";\n\nconst { registry } = defineRegistry(catalog, {\n  components: {\n    Card: (renderProps) =\u003e \u003cdiv\u003e{renderProps.children}\u003c/div\u003e,\n    Button: (renderProps) =\u003e (\n      \u003cbutton onClick={() =\u003e renderProps.emit(\"press\")}\u003e\n        {renderProps.element.props.label as string}\n      \u003c/button\u003e\n    ),\n  },\n});\n\n\u003cRenderer spec={spec} registry={registry} /\u003e;\n```\n\n### shadcn/ui (Web)\n\n```tsx\nimport { defineCatalog } from \"@json-render/core\";\nimport { schema } from \"@json-render/react/schema\";\nimport { defineRegistry, Renderer } from \"@json-render/react\";\nimport { shadcnComponentDefinitions } from \"@json-render/shadcn/catalog\";\nimport { shadcnComponents } from \"@json-render/shadcn\";\n\n// Pick components from the 36 standard definitions\nconst catalog = defineCatalog(schema, {\n  components: {\n    Card: shadcnComponentDefinitions.Card,\n    Stack: shadcnComponentDefinitions.Stack,\n    Heading: shadcnComponentDefinitions.Heading,\n    Button: shadcnComponentDefinitions.Button,\n  },\n  actions: {},\n});\n\n// Use matching implementations\nconst { registry } = defineRegistry(catalog, {\n  components: {\n    Card: shadcnComponents.Card,\n    Stack: shadcnComponents.Stack,\n    Heading: shadcnComponents.Heading,\n    Button: shadcnComponents.Button,\n  },\n});\n\n\u003cRenderer spec={spec} registry={registry} /\u003e;\n```\n\n### React Native (Mobile)\n\n```tsx\nimport { defineCatalog } from \"@json-render/core\";\nimport { schema } from \"@json-render/react-native/schema\";\nimport {\n  standardComponentDefinitions,\n  standardActionDefinitions,\n} from \"@json-render/react-native/catalog\";\nimport { defineRegistry, Renderer } from \"@json-render/react-native\";\n\n// 25+ standard components included\nconst catalog = defineCatalog(schema, {\n  components: { ...standardComponentDefinitions },\n  actions: standardActionDefinitions,\n});\n\nconst { registry } = defineRegistry(catalog, { components: {} });\n\u003cRenderer spec={spec} registry={registry} /\u003e;\n```\n\n### Remotion (Video)\n\n```tsx\nimport { Player } from \"@remotion/player\";\nimport {\n  Renderer,\n  schema,\n  standardComponentDefinitions,\n} from \"@json-render/remotion\";\n\n// Timeline spec format\nconst spec = {\n  composition: {\n    id: \"video\",\n    fps: 30,\n    width: 1920,\n    height: 1080,\n    durationInFrames: 300,\n  },\n  tracks: [{ id: \"main\", name: \"Main\", type: \"video\", enabled: true }],\n  clips: [\n    {\n      id: \"clip-1\",\n      trackId: \"main\",\n      component: \"TitleCard\",\n      props: { title: \"Hello\" },\n      from: 0,\n      durationInFrames: 90,\n    },\n  ],\n  audio: { tracks: [] },\n};\n\n\u003cPlayer\n  component={Renderer}\n  inputProps={{ spec }}\n  durationInFrames={spec.composition.durationInFrames}\n  fps={spec.composition.fps}\n  compositionWidth={spec.composition.width}\n  compositionHeight={spec.composition.height}\n/\u003e;\n```\n\n### React PDF (Documents)\n\n```typescript\nimport { renderToBuffer } from \"@json-render/react-pdf\";\n\nconst spec = {\n  root: \"doc\",\n  elements: {\n    doc: {\n      type: \"Document\",\n      props: { title: \"Invoice\" },\n      children: [\"page-1\"],\n    },\n    \"page-1\": {\n      type: \"Page\",\n      props: { size: \"A4\" },\n      children: [\"heading-1\", \"table-1\"],\n    },\n    \"heading-1\": {\n      type: \"Heading\",\n      props: { text: \"Invoice #1234\", level: \"h1\" },\n      children: [],\n    },\n    \"table-1\": {\n      type: \"Table\",\n      props: {\n        columns: [\n          { header: \"Item\", width: \"60%\" },\n          { header: \"Price\", width: \"40%\", align: \"right\" },\n        ],\n        rows: [\n          [\"Widget A\", \"$10.00\"],\n          [\"Widget B\", \"$25.00\"],\n        ],\n      },\n      children: [],\n    },\n  },\n};\n\n// Render to buffer, stream, or file\nconst buffer = await renderToBuffer(spec);\n```\n\n### React Email (Email)\n\n```typescript\nimport { renderToHtml } from \"@json-render/react-email\";\nimport { schema, standardComponentDefinitions } from \"@json-render/react-email\";\nimport { defineCatalog } from \"@json-render/core\";\n\nconst catalog = defineCatalog(schema, {\n  components: standardComponentDefinitions,\n});\n\nconst spec = {\n  root: \"html-1\",\n  elements: {\n    \"html-1\": {\n      type: \"Html\",\n      props: { lang: \"en\", dir: \"ltr\" },\n      children: [\"head-1\", \"body-1\"],\n    },\n    \"head-1\": { type: \"Head\", props: {}, children: [] },\n    \"body-1\": {\n      type: \"Body\",\n      props: { style: { backgroundColor: \"#f6f9fc\" } },\n      children: [\"container-1\"],\n    },\n    \"container-1\": {\n      type: \"Container\",\n      props: {\n        style: { maxWidth: \"600px\", margin: \"0 auto\", padding: \"20px\" },\n      },\n      children: [\"heading-1\", \"text-1\"],\n    },\n    \"heading-1\": { type: \"Heading\", props: { text: \"Welcome\" }, children: [] },\n    \"text-1\": {\n      type: \"Text\",\n      props: { text: \"Thanks for signing up.\" },\n      children: [],\n    },\n  },\n};\n\nconst html = await renderToHtml(spec);\n```\n\n### Image (SVG/PNG)\n\n```typescript\nimport { renderToPng } from \"@json-render/image/render\";\n\nconst spec = {\n  root: \"frame\",\n  elements: {\n    frame: {\n      type: \"Frame\",\n      props: { width: 1200, height: 630, backgroundColor: \"#1a1a2e\" },\n      children: [\"heading\"],\n    },\n    heading: {\n      type: \"Heading\",\n      props: { text: \"Hello World\", level: \"h1\", color: \"#ffffff\" },\n      children: [],\n    },\n  },\n};\n\n// Render to PNG (requires @resvg/resvg-js)\nconst png = await renderToPng(spec, { fonts });\n\n// Or render to SVG string\nimport { renderToSvg } from \"@json-render/image/render\";\nconst svg = await renderToSvg(spec, { fonts });\n```\n\n### Three.js (3D)\n\n```tsx\nimport { defineCatalog } from \"@json-render/core\";\nimport { schema, defineRegistry } from \"@json-render/react\";\nimport {\n  threeComponentDefinitions,\n  threeComponents,\n  ThreeCanvas,\n} from \"@json-render/react-three-fiber\";\n\nconst catalog = defineCatalog(schema, {\n  components: {\n    Box: threeComponentDefinitions.Box,\n    Sphere: threeComponentDefinitions.Sphere,\n    AmbientLight: threeComponentDefinitions.AmbientLight,\n    DirectionalLight: threeComponentDefinitions.DirectionalLight,\n    OrbitControls: threeComponentDefinitions.OrbitControls,\n  },\n  actions: {},\n});\n\nconst { registry } = defineRegistry(catalog, {\n  components: {\n    Box: threeComponents.Box,\n    Sphere: threeComponents.Sphere,\n    AmbientLight: threeComponents.AmbientLight,\n    DirectionalLight: threeComponents.DirectionalLight,\n    OrbitControls: threeComponents.OrbitControls,\n  },\n});\n\n\u003cThreeCanvas\n  spec={spec}\n  registry={registry}\n  shadows\n  camera={{ position: [5, 5, 5], fov: 50 }}\n  style={{ width: \"100%\", height: \"100vh\" }}\n/\u003e;\n```\n\n### Next.js (Full Apps)\n\n```typescript\nimport type { NextAppSpec } from \"@json-render/next\";\nimport { createNextApp } from \"@json-render/next/server\";\nimport { NextAppProvider } from \"@json-render/next\";\n\nconst spec: NextAppSpec = {\n  metadata: { title: { default: \"My App\", template: \"%s | My App\" } },\n  layouts: {\n    main: {\n      root: \"shell\",\n      elements: {\n        shell: { type: \"Container\", props: {}, children: [\"nav\", \"slot\"] },\n        nav: { type: \"NavBar\", props: {}, children: [] },\n        slot: { type: \"Slot\", props: {}, children: [] },\n      },\n    },\n  },\n  routes: {\n    \"/\": {\n      layout: \"main\",\n      metadata: { title: \"Home\" },\n      page: {\n        root: \"hero\",\n        elements: {\n          hero: { type: \"Card\", props: { title: \"Welcome\" }, children: [] },\n        },\n      },\n    },\n  },\n};\n\n// Server: creates Page, generateMetadata, generateStaticParams\nconst app = createNextApp({ spec });\n\n// Client: wrap your layout with NextAppProvider\n// \u003cNextAppProvider registry={registry} handlers={handlers}\u003e\n//   {children}\n// \u003c/NextAppProvider\u003e\n```\n\n### shadcn-svelte (Svelte)\n\n```typescript\nimport { defineCatalog } from \"@json-render/core\";\nimport { schema } from \"@json-render/svelte/schema\";\nimport { defineRegistry, Renderer } from \"@json-render/svelte\";\nimport { shadcnComponentDefinitions } from \"@json-render/shadcn-svelte/catalog\";\nimport { shadcnComponents } from \"@json-render/shadcn-svelte\";\n\nconst catalog = defineCatalog(schema, {\n  components: {\n    Card: shadcnComponentDefinitions.Card,\n    Stack: shadcnComponentDefinitions.Stack,\n    Heading: shadcnComponentDefinitions.Heading,\n    Button: shadcnComponentDefinitions.Button,\n  },\n  actions: {},\n});\n\nconst { registry } = defineRegistry(catalog, {\n  components: {\n    Card: shadcnComponents.Card,\n    Stack: shadcnComponents.Stack,\n    Heading: shadcnComponents.Heading,\n    Button: shadcnComponents.Button,\n  },\n});\n\n// In your Svelte component:\n// \u003cRenderer spec={spec} registry={registry} /\u003e\n```\n\n### Ink (Terminal)\n\n```tsx\nimport { defineCatalog } from \"@json-render/core\";\nimport {\n  schema,\n  standardComponentDefinitions,\n  standardActionDefinitions,\n  defineRegistry,\n  Renderer,\n  JSONUIProvider,\n} from \"@json-render/ink\";\n\nconst catalog = defineCatalog(schema, {\n  components: { ...standardComponentDefinitions },\n  actions: standardActionDefinitions,\n});\n\nconst { registry } = defineRegistry(catalog, { components: {} });\n\nconst spec = {\n  root: \"card-1\",\n  elements: {\n    \"card-1\": {\n      type: \"Card\",\n      props: { title: \"Status\" },\n      children: [\"status-1\"],\n    },\n    \"status-1\": {\n      type: \"StatusLine\",\n      props: { label: \"Build\", status: \"success\" },\n      children: [],\n    },\n  },\n};\n\n\u003cJSONUIProvider initialState={{}}\u003e\n  \u003cRenderer spec={spec} registry={registry} /\u003e\n\u003c/JSONUIProvider\u003e;\n```\n\n## Features\n\n### Streaming (SpecStream)\n\nStream AI responses progressively:\n\n```typescript\nimport { createSpecStreamCompiler } from \"@json-render/core\";\n\nconst compiler = createSpecStreamCompiler\u003cMySpec\u003e();\n\n// Process chunks as they arrive\nconst { result, newPatches } = compiler.push(chunk);\nsetSpec(result); // Update UI with partial result\n\n// Get final result\nconst finalSpec = compiler.getResult();\n```\n\n### AI Prompt Generation\n\nGenerate system prompts from your catalog:\n\n```typescript\nconst systemPrompt = catalog.prompt();\n// Includes component descriptions, props schemas, available actions\n```\n\n### Conditional Visibility\n\n```json\n{\n  \"type\": \"Alert\",\n  \"props\": { \"message\": \"Error occurred\" },\n  \"visible\": [\n    { \"$state\": \"/form/hasError\" },\n    { \"$state\": \"/form/errorDismissed\", \"not\": true }\n  ]\n}\n```\n\n### Dynamic Props\n\nAny prop value can be data-driven using expressions:\n\n```json\n{\n  \"type\": \"Icon\",\n  \"props\": {\n    \"name\": {\n      \"$cond\": { \"$state\": \"/activeTab\", \"eq\": \"home\" },\n      \"$then\": \"home\",\n      \"$else\": \"home-outline\"\n    },\n    \"color\": {\n      \"$cond\": { \"$state\": \"/activeTab\", \"eq\": \"home\" },\n      \"$then\": \"#007AFF\",\n      \"$else\": \"#8E8E93\"\n    }\n  }\n}\n```\n\nExpression forms:\n\n- **`{ \"$state\": \"/state/key\" }`** - reads a value from the state model\n- **`{ \"$cond\": \u003ccondition\u003e, \"$then\": \u003cvalue\u003e, \"$else\": \u003cvalue\u003e }`** - evaluates a condition and picks a branch\n- **`{ \"$template\": \"Hello, ${/user/name}!\" }`** - interpolates state values into strings\n- **`{ \"$computed\": \"fn\", \"args\": { ... } }`** - calls a registered function with resolved args\n\n### Actions\n\nComponents can trigger actions, including the built-in `setState` action:\n\n```json\n{\n  \"type\": \"Pressable\",\n  \"props\": {\n    \"action\": \"setState\",\n    \"actionParams\": { \"statePath\": \"/activeTab\", \"value\": \"home\" }\n  },\n  \"children\": [\"home-icon\"]\n}\n```\n\nThe `setState` action updates the state model directly, which re-evaluates visibility conditions and dynamic prop expressions.\n\n### State Watchers\n\nReact to state changes by triggering actions:\n\n```json\n{\n  \"type\": \"Select\",\n  \"props\": {\n    \"value\": { \"$bindState\": \"/form/country\" },\n    \"options\": [\"US\", \"Canada\", \"UK\"]\n  },\n  \"watch\": {\n    \"/form/country\": {\n      \"action\": \"loadCities\",\n      \"params\": { \"country\": { \"$state\": \"/form/country\" } }\n    }\n  }\n}\n```\n\n`watch` is a top-level field on elements (sibling of `type`/`props`/`children`). Watchers fire when the watched value changes, not on initial render.\n\n---\n\n## Demo\n\n```bash\ngit clone https://github.com/vercel-labs/json-render\ncd json-render\npnpm install\npnpm dev\n```\n\n- http://json-render.localhost:1355 - Docs \u0026 Playground\n- http://dashboard-demo.json-render.localhost:1355 - Example Dashboard\n- http://react-email-demo.json-render.localhost:1355 - React Email Example\n- http://remotion-demo.json-render.localhost:1355 - Remotion Video Example\n- Chat Example: run `pnpm dev` in `examples/chat`\n- Svelte Example: run `pnpm dev` in `examples/svelte` or `examples/svelte-chat`\n- Vue Example: run `pnpm dev` in `examples/vue`\n- Vite Renderers (React + Vue + Svelte + Solid): run `pnpm dev` in `examples/vite-renderers`\n- React Native example: run `npx expo start` in `examples/react-native`\n\n## How It Works\n\n```mermaid\nflowchart LR\n    A[User Prompt] --\u003e B[AI + Catalog]\n    B --\u003e C[JSON Spec]\n    C --\u003e D[Renderer]\n\n    B -.- E([guardrailed])\n    C -.- F([predictable])\n    D -.- G([streamed])\n```\n\n1. **Define the guardrails** - what components, actions, and data bindings AI can use\n2. **Prompt** - describe what you want in natural language\n3. **AI generates JSON** - output is always predictable, constrained to your catalog\n4. **Render fast** - stream and render progressively as the model responds\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvercel-labs%2Fjson-render","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvercel-labs%2Fjson-render","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvercel-labs%2Fjson-render/lists"}