Claude Agent Skill · by Aradotso

Json Render Generative Ui

Install Json Render Generative Ui skill for Claude Code from aradotso/trending-skills.

Works with Paperclip

How Json Render Generative Ui fits into a Paperclip company.

Json Render Generative Ui drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md721 lines
Expand
---name: json-render-generative-uidescription: Generative UI framework that renders AI-generated JSON specs into type-safe UI components across React, Vue, Svelte, Solid, React Native, video, PDF, and email.triggers:  - use json-render to generate UI  - render AI-generated components with json-render  - generative UI with json-render  - create a json-render catalog  - stream AI UI with json-render  - json-render shadcn components  - build dynamic UI from AI prompts  - json-render react renderer setup--- # json-render Generative UI Framework > Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection. json-render is a **Generative UI framework** that lets AI generate dynamic interfaces from natural language prompts, constrained to a predefined component catalog. AI outputs JSON; json-render renders it safely and predictably across any platform. ## Installation ```bash# React (core)npm install @json-render/core @json-render/react # React + shadcn/ui (36 pre-built components)npm install @json-render/shadcn # React Nativenpm install @json-render/core @json-render/react-native # Vuenpm install @json-render/core @json-render/vue # Sveltenpm install @json-render/core @json-render/svelte # SolidJSnpm install @json-render/core @json-render/solid # Video (Remotion)npm install @json-render/core @json-render/remotion # PDFnpm install @json-render/core @json-render/react-pdf # Emailnpm install @json-render/core @json-render/react-email @react-email/components @react-email/render # 3D (React Three Fiber)npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three # OG Images / SVG / PNGnpm install @json-render/core @json-render/image # State management adaptersnpm install @json-render/zustand   # or redux, jotai, xstate # MCP integration (Claude, ChatGPT, Cursor)npm install @json-render/mcp # YAML wire formatnpm install @json-render/yaml``` ## Core Concepts | Concept | Description ||---|---|| **Catalog** | Defines allowed components and actions (the guardrails for AI) || **Spec** | AI-generated JSON describing which components to render and with what props || **Registry** | Maps catalog component names to actual render implementations || **Renderer** | Platform-specific component that takes a spec + registry and renders UI || **Actions** | Named events AI can trigger (e.g. `export_report`, `refresh_data`) | ## Spec Format The flat spec format uses a root key + elements map: ```typescriptconst spec = {  root: "card-1",  elements: {    "card-1": {      type: "Card",      props: { title: "Dashboard" },      children: ["metric-1", "metric-2", "button-1"],    },    "metric-1": {      type: "Metric",      props: { label: "Revenue", value: "124000", format: "currency" },      children: [],    },    "metric-2": {      type: "Metric",      props: { label: "Growth", value: "0.18", format: "percent" },      children: [],    },    "button-1": {      type: "Button",      props: { label: "Export Report", action: "export_report" },      children: [],    },  },};``` ## Step 1: Define a Catalog ```typescriptimport { defineCatalog } from "@json-render/core";import { schema } from "@json-render/react/schema";import { z } from "zod"; const catalog = defineCatalog(schema, {  components: {    Card: {      props: z.object({ title: z.string() }),      description: "A card container with a title",    },    Metric: {      props: z.object({        label: z.string(),        value: z.string(),        format: z.enum(["currency", "percent", "number"]).nullable(),      }),      description: "Displays a single metric value with optional formatting",    },    Button: {      props: z.object({        label: z.string(),        action: z.string(),      }),      description: "Clickable button that triggers an action",    },    Stack: {      props: z.object({        direction: z.enum(["row", "column"]).default("column"),        gap: z.number().optional(),      }),      description: "Layout container that stacks children",    },  },  actions: {    export_report: { description: "Export the current dashboard to PDF" },    refresh_data: { description: "Refresh all metric data" },    navigate: {      description: "Navigate to a page",      payload: z.object({ path: z.string() }),    },  },});``` ## Step 2: Define a Registry (React) ```tsximport { defineRegistry, Renderer } from "@json-render/react"; function format(value: string, fmt: string | null): string {  if (fmt === "currency") return `$${Number(value).toLocaleString()}`;  if (fmt === "percent") return `${(Number(value) * 100).toFixed(1)}%`;  return value;} const { registry } = defineRegistry(catalog, {  components: {    Card: ({ props, children }) => (      <div className="rounded-lg border p-4 shadow-sm">        <h3 className="text-lg font-semibold mb-3">{props.title}</h3>        {children}      </div>    ),     Metric: ({ props }) => (      <div className="flex flex-col">        <span className="text-sm text-gray-500">{props.label}</span>        <span className="text-2xl font-bold">          {format(props.value, props.format)}        </span>      </div>    ),     Button: ({ props, emit }) => (      <button        className="px-4 py-2 bg-blue-600 text-white rounded"        onClick={() => emit("press")}      >        {props.label}      </button>    ),     Stack: ({ props, children }) => (      <div        style={{          display: "flex",          flexDirection: props.direction ?? "column",          gap: props.gap ?? 8,        }}      >        {children}      </div>    ),  },});``` ## Step 3: Render the Spec ```tsximport { Renderer } from "@json-render/react"; function Dashboard({ spec, onAction }) {  return (    <Renderer      spec={spec}      registry={registry}      onAction={(action, payload) => {        console.log("Action triggered:", action, payload);        onAction?.(action, payload);      }}    />  );}``` ## Generating Specs with AI (Vercel AI SDK) ```typescriptimport { generateObject } from "ai";import { openai } from "@ai-sdk/openai";import { getCatalogSchema, getCatalogPrompt } from "@json-render/core"; async function generateDashboard(userPrompt: string) {  const { object: spec } = await generateObject({    model: openai("gpt-4o"),    schema: getCatalogSchema(catalog),    system: getCatalogPrompt(catalog),    prompt: userPrompt,  });   return spec;} // Usageconst spec = await generateDashboard(  "Create a sales dashboard showing revenue, conversion rate, and an export button");``` ## Streaming Specs ```tsximport { streamObject } from "ai";import { openai } from "@ai-sdk/openai";import { getCatalogSchema, getCatalogPrompt, parseSpecStream } from "@json-render/core";import { Renderer } from "@json-render/react";import { useState, useEffect } from "react"; function StreamingDashboard({ prompt }: { prompt: string }) {  const [spec, setSpec] = useState(null);   useEffect(() => {    async function stream() {      const { partialObjectStream } = await streamObject({        model: openai("gpt-4o"),        schema: getCatalogSchema(catalog),        system: getCatalogPrompt(catalog),        prompt,      });       for await (const partial of partialObjectStream) {        setSpec(partial); // Renderer handles partial specs gracefully      }    }    stream();  }, [prompt]);   if (!spec) return <div>Generating UI...</div>;  return <Renderer spec={spec} registry={registry} />;}``` ## Using Pre-built shadcn/ui Components ```tsximport { defineCatalog } from "@json-render/core";import { schema } from "@json-render/react/schema";import { defineRegistry, Renderer } from "@json-render/react";import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";import { shadcnComponents } from "@json-render/shadcn"; // Pick any of the 36 available shadcn componentsconst catalog = defineCatalog(schema, {  components: {    Card: shadcnComponentDefinitions.Card,    Stack: shadcnComponentDefinitions.Stack,    Heading: shadcnComponentDefinitions.Heading,    Text: shadcnComponentDefinitions.Text,    Button: shadcnComponentDefinitions.Button,    Badge: shadcnComponentDefinitions.Badge,    Table: shadcnComponentDefinitions.Table,    Chart: shadcnComponentDefinitions.Chart,    Input: shadcnComponentDefinitions.Input,    Select: shadcnComponentDefinitions.Select,  },  actions: {    submit: { description: "Submit a form" },    export: { description: "Export data" },  },}); const { registry } = defineRegistry(catalog, {  components: {    Card: shadcnComponents.Card,    Stack: shadcnComponents.Stack,    Heading: shadcnComponents.Heading,    Text: shadcnComponents.Text,    Button: shadcnComponents.Button,    Badge: shadcnComponents.Badge,    Table: shadcnComponents.Table,    Chart: shadcnComponents.Chart,    Input: shadcnComponents.Input,    Select: shadcnComponents.Select,  },}); function AIPage({ spec }) {  return <Renderer spec={spec} registry={registry} />;}``` ## Vue Renderer ```typescriptimport { h, defineComponent } from "vue";import { defineCatalog } from "@json-render/core";import { schema } from "@json-render/vue/schema";import { defineRegistry, Renderer } from "@json-render/vue";import { z } from "zod"; const catalog = defineCatalog(schema, {  components: {    Card: {      props: z.object({ title: z.string() }),      description: "Card container",    },    Button: {      props: z.object({ label: z.string() }),      description: "Button",    },  },  actions: {    click: { description: "Button clicked" },  },}); const { registry } = defineRegistry(catalog, {  components: {    Card: ({ props, children }) =>      h("div", { class: "card" }, [        h("h3", null, props.title),        children,      ]),    Button: ({ props, emit }) =>      h("button", { onClick: () => emit("click") }, props.label),  },}); // In your Vue SFC:// <template>//   <Renderer :spec="spec" :registry="registry" />// </template>``` ## React Native Renderer ```tsximport { defineCatalog } from "@json-render/core";import { schema } from "@json-render/react-native/schema";import {  standardComponentDefinitions,  standardActionDefinitions,} from "@json-render/react-native/catalog";import { defineRegistry, Renderer } from "@json-render/react-native"; // 25+ standard mobile components out of the boxconst catalog = defineCatalog(schema, {  components: { ...standardComponentDefinitions },  actions: standardActionDefinitions,}); const { registry } = defineRegistry(catalog, {  components: {}, // use all standard implementations}); export function AIScreen({ spec }) {  return <Renderer spec={spec} registry={registry} />;}``` ## PDF Generation ```typescriptimport { renderToBuffer } from "@json-render/react-pdf"; const invoiceSpec = {  root: "doc",  elements: {    doc: {      type: "Document",      props: { title: "Invoice #1234" },      children: ["page-1"],    },    "page-1": {      type: "Page",      props: { size: "A4" },      children: ["heading-1", "table-1"],    },    "heading-1": {      type: "Heading",      props: { text: "Invoice #1234", level: "h1" },      children: [],    },    "table-1": {      type: "Table",      props: {        columns: [          { header: "Item", width: "60%" },          { header: "Amount", width: "40%", align: "right" },        ],        rows: [          ["Widget A", "$10.00"],          ["Widget B", "$25.00"],          ["Total", "$35.00"],        ],      },      children: [],    },  },}; // Returns a Buffer you can send as a responseconst buffer = await renderToBuffer(invoiceSpec); // In a Next.js route handler:export async function GET() {  const buffer = await renderToBuffer(invoiceSpec);  return new Response(buffer, {    headers: { "Content-Type": "application/pdf" },  });}``` ## Email Generation ```typescriptimport { renderToHtml } from "@json-render/react-email";import { schema, standardComponentDefinitions } from "@json-render/react-email";import { defineCatalog } from "@json-render/core"; const catalog = defineCatalog(schema, {  components: standardComponentDefinitions,}); const emailSpec = {  root: "html-1",  elements: {    "html-1": {      type: "Html",      props: { lang: "en" },      children: ["head-1", "body-1"],    },    "head-1": { type: "Head", props: {}, children: [] },    "body-1": {      type: "Body",      props: { style: { backgroundColor: "#f6f9fc" } },      children: ["container-1"],    },    "container-1": {      type: "Container",      props: { style: { maxWidth: "600px", margin: "0 auto" } },      children: ["heading-1", "text-1", "button-1"],    },    "heading-1": {      type: "Heading",      props: { text: "Welcome aboard!" },      children: [],    },    "text-1": {      type: "Text",      props: { text: "Thanks for signing up. Click below to get started." },      children: [],    },    "button-1": {      type: "Button",      props: { text: "Get Started", href: "https://example.com" },      children: [],    },  },}; const html = await renderToHtml(emailSpec);``` ## MCP Integration (Claude, ChatGPT, Cursor) ```typescriptimport { createMCPServer } from "@json-render/mcp"; const server = createMCPServer({  catalog,  name: "my-ui-server",  version: "1.0.0",}); server.start();``` ## State Management Integration ```typescriptimport { create } from "zustand";import { createZustandAdapter } from "@json-render/zustand"; const useStore = create((set) => ({  data: {},  setData: (data) => set({ data }),})); const stateStore = createZustandAdapter(useStore); // Pass to Renderer for action handling with state<Renderer spec={spec} registry={registry} stateStore={stateStore} />;``` ## YAML Wire Format ```typescriptimport { parseYAML, toYAML } from "@json-render/yaml"; // AI can output YAML instead of JSON (often more token-efficient)const yamlSpec = `root: card-1elements:  card-1:    type: Card    props:      title: Hello World    children: [button-1]  button-1:    type: Button    props:      label: Click Me    children: []`; const spec = parseYAML(yamlSpec);``` ## Full Next.js App Router Example ```tsx// app/dashboard/page.tsximport { generateObject } from "ai";import { openai } from "@ai-sdk/openai";import { getCatalogSchema, getCatalogPrompt } from "@json-render/core";import { DashboardRenderer } from "./DashboardRenderer";import { catalog } from "@/lib/catalog"; export default async function DashboardPage({  searchParams,}: {  searchParams: { q?: string };}) {  const prompt = searchParams.q ?? "Show me a sales overview dashboard";   const { object: spec } = await generateObject({    model: openai("gpt-4o"),    schema: getCatalogSchema(catalog),    system: getCatalogPrompt(catalog),    prompt,  });   return <DashboardRenderer spec={spec} />;}``` ```tsx// app/dashboard/DashboardRenderer.tsx"use client";import { Renderer } from "@json-render/react";import { registry } from "@/lib/registry";import { useRouter } from "next/navigation"; export function DashboardRenderer({ spec }) {  const router = useRouter();   return (    <Renderer      spec={spec}      registry={registry}      onAction={(action, payload) => {        switch (action) {          case "navigate":            router.push(payload.path);            break;          case "export_report":            window.open("/api/export", "_blank");            break;          case "refresh_data":            router.refresh();            break;        }      }}    />  );}``` ## Common Patterns ### Conditional Component Availability ```typescript// Restrict catalog based on user rolefunction getCatalogForRole(role: "admin" | "viewer") {  const base = { Card, Stack, Heading, Text, Metric };  const adminOnly = role === "admin" ? { Button, Form, Table } : {};  const adminActions = role === "admin"    ? { export: { description: "Export data" } }    : {};   return defineCatalog(schema, {    components: { ...base, ...adminOnly },    actions: adminOnly ? adminActions : {},  });}``` ### Dynamic Props with Runtime Data ```tsx// Components can fetch their own dataconst { registry } = defineRegistry(catalog, {  components: {    LiveMetric: ({ props }) => {      const { data } = useSWR(`/api/metrics/${props.metricId}`);      return (        <div>          <span>{props.label}</span>          <span>{data?.value ?? "..."}</span>        </div>      );    },  },});``` ### Type-Safe Action Handling ```typescriptimport { type ActionHandler } from "@json-render/core"; const handleAction: ActionHandler<typeof catalog> = (action, payload) => {  // action and payload are fully typed based on your catalog definition  if (action === "navigate") {    router.push(payload.path); // payload.path is typed as string  }};``` ## Troubleshooting | Problem | Cause | Fix ||---|---|---|| AI generates unknown component type | Component not in catalog | Add component to `defineCatalog` or update AI prompt || Props validation error | AI hallucinated a prop | Tighten Zod schema, add `.strict()` or `.describe()` hints || Renderer shows nothing | `root` key doesn't match an `elements` key | Check spec structure; root must reference a valid element ID || Partial spec renders incorrectly | Streaming not handled | Use `parseSpecStream` utility or check for null elements before render || Actions not firing | `onAction` not passed to `Renderer` | Pass `onAction` prop to `<Renderer>` || shadcn components unstyled | Missing Tailwind config | Ensure `@json-render/shadcn` paths are in `tailwind.config.js` content array || TypeScript errors in registry | Catalog/registry mismatch | Ensure `defineRegistry(catalog, ...)` uses the same `catalog` instance | ## Environment Variables ```bash# For AI generation (use your preferred provider)OPENAI_API_KEY=your_key_hereANTHROPIC_API_KEY=your_key_here # For MCP serverMCP_SERVER_PORT=3001``` ## Key API Reference ```typescript// CoredefineCatalog(schema, { components, actions })  // Define guardrailsgetCatalogSchema(catalog)                        // Get Zod schema for AIgetCatalogPrompt(catalog)                        // Get system prompt for AI // ReactdefineRegistry(catalog, { components })          // Create typed registry<Renderer spec={spec} registry={registry} onAction={fn} /> // Core utilitiesparseSpecStream(stream)    // Parse streaming partial specstoYAML(spec)              // Convert spec to YAMLparseYAML(yaml)           // Parse YAML spec to JSON // PDFrenderToBuffer(spec)       // → BufferrenderToStream(spec)       // → ReadableStream // EmailrenderToHtml(spec)         // → HTML stringrenderToText(spec)         // → plain text string```