json-render-generative-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

json-render Generative UI Framework

json-render 生成式UI框架

Skill by 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.
ara.so提供的Skill — 属于Daily 2026 Skills合集。
json-render是一款生成式UI框架,允许AI根据自然语言提示生成动态界面,并受限于预定义的组件目录。AI输出JSON;json-render可在任意平台安全、可预测地渲染这些JSON。

Installation

安装

bash
undefined
bash
undefined

React (core)

React(核心)

npm install @json-render/core @json-render/react
npm install @json-render/core @json-render/react

React + shadcn/ui (36 pre-built components)

React + shadcn/ui(包含36个预构建组件)

npm install @json-render/shadcn
npm install @json-render/shadcn

React Native

React Native

npm install @json-render/core @json-render/react-native
npm install @json-render/core @json-render/react-native

Vue

Vue

npm install @json-render/core @json-render/vue
npm install @json-render/core @json-render/vue

Svelte

Svelte

npm install @json-render/core @json-render/svelte
npm install @json-render/core @json-render/svelte

SolidJS

SolidJS

npm install @json-render/core @json-render/solid
npm install @json-render/core @json-render/solid

Video (Remotion)

视频(Remotion)

npm install @json-render/core @json-render/remotion
npm install @json-render/core @json-render/remotion

PDF

PDF

npm install @json-render/core @json-render/react-pdf
npm install @json-render/core @json-render/react-pdf

Email

邮件

npm install @json-render/core @json-render/react-email @react-email/components @react-email/render
npm install @json-render/core @json-render/react-email @react-email/components @react-email/render

3D (React Three Fiber)

3D(React Three Fiber)

npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three
npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three

OG Images / SVG / PNG

OG图片/SVG/PNG

npm install @json-render/core @json-render/image
npm install @json-render/core @json-render/image

State management adapters

状态管理适配器

npm install @json-render/zustand # or redux, jotai, xstate
npm install @json-render/zustand # 或 redux, jotai, xstate

MCP integration (Claude, ChatGPT, Cursor)

MCP集成(Claude、ChatGPT、Cursor)

npm install @json-render/mcp
npm install @json-render/mcp

YAML wire format

YAML传输格式

npm install @json-render/yaml
undefined
npm install @json-render/yaml
undefined

Core Concepts

核心概念

ConceptDescription
CatalogDefines allowed components and actions (the guardrails for AI)
SpecAI-generated JSON describing which components to render and with what props
RegistryMaps catalog component names to actual render implementations
RendererPlatform-specific component that takes a spec + registry and renders UI
ActionsNamed events AI can trigger (e.g.
export_report
,
refresh_data
)
概念描述
Catalog(组件目录)定义允许使用的组件和操作(为AI设置的约束规则)
Spec(规格)AI生成的JSON,描述要渲染的组件及其属性
Registry(注册表)将目录中的组件名称映射到实际的渲染实现
Renderer(渲染器)平台专属组件,接收规格和注册表并渲染UI
Actions(操作)AI可触发的命名事件(例如
export_report
refresh_data

Spec Format

规格格式

The flat spec format uses a root key + elements map:
typescript
const 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: [],
    },
  },
};
扁平化规格格式使用根键+元素映射:
typescript
const 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

步骤1:定义组件目录

typescript
import { 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() }),
    },
  },
});
typescript
import { 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: "带标题的卡片容器",
    },
    Metric: {
      props: z.object({
        label: z.string(),
        value: z.string(),
        format: z.enum(["currency", "percent", "number"]).nullable(),
      }),
      description: "显示单个指标值,支持可选格式化",
    },
    Button: {
      props: z.object({
        label: z.string(),
        action: z.string(),
      }),
      description: "可点击按钮,触发指定操作",
    },
    Stack: {
      props: z.object({
        direction: z.enum(["row", "column"]).default("column"),
        gap: z.number().optional(),
      }),
      description: "用于堆叠子元素的布局容器",
    },
  },
  actions: {
    export_report: { description: "将当前仪表板导出为PDF" },
    refresh_data: { description: "刷新所有指标数据" },
    navigate: {
      description: "跳转到指定页面",
      payload: z.object({ path: z.string() }),
    },
  },
});

Step 2: Define a Registry (React)

步骤2:定义注册表(React)

tsx
import { 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>
    ),
  },
});
tsx
import { 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

步骤3:渲染规格

tsx
import { 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);
      }}
    />
  );
}
tsx
import { Renderer } from "@json-render/react";

function Dashboard({ spec, onAction }) {
  return (
    <Renderer
      spec={spec}
      registry={registry}
      onAction={(action, payload) => {
        console.log("触发操作:", action, payload);
        onAction?.(action, payload);
      }}
    />
  );
}

Generating Specs with AI (Vercel AI SDK)

使用AI生成规格(Vercel AI SDK)

typescript
import { 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;
}

// Usage
const spec = await generateDashboard(
  "Create a sales dashboard showing revenue, conversion rate, and an export button"
);
typescript
import { 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;
}

// 使用示例
const spec = await generateDashboard(
  "创建一个显示收入、转化率和导出按钮的销售仪表板"
);

Streaming Specs

流式传输规格

tsx
import { 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} />;
}
tsx
import { 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可优雅处理部分规格
      }
    }
    stream();
  }, [prompt]);

  if (!spec) return <div>正在生成UI...</div>;
  return <Renderer spec={spec} registry={registry} />;
}

Using Pre-built shadcn/ui Components

使用预构建的shadcn/ui组件

tsx
import { 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 components
const 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} />;
}
tsx
import { 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";

// 从36个可用的shadcn组件中选择
const 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: "提交表单" },
    export: { description: "导出数据" },
  },
});

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

Vue渲染器

typescript
import { 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>
typescript
import { 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: "卡片容器",
    },
    Button: {
      props: z.object({ label: z.string() }),
      description: "按钮",
    },
  },
  actions: {
    click: { description: "按钮被点击" },
  },
});

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),
  },
});

// 在Vue单文件组件中使用:
// <template>
//   <Renderer :spec="spec" :registry="registry" />
// </template>

React Native Renderer

React Native渲染器

tsx
import { 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 box
const 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} />;
}
tsx
import { 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+标准移动端组件
const catalog = defineCatalog(schema, {
  components: { ...standardComponentDefinitions },
  actions: standardActionDefinitions,
});

const { registry } = defineRegistry(catalog, {
  components: {}, // 使用所有标准实现
});

export function AIScreen({ spec }) {
  return <Renderer spec={spec} registry={registry} />;
}

PDF Generation

PDF生成

typescript
import { 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 response
const 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" },
  });
}
typescript
import { 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: [],
    },
  },
};

// 返回可作为响应发送的Buffer
const buffer = await renderToBuffer(invoiceSpec);

// 在Next.js路由处理器中使用:
export async function GET() {
  const buffer = await renderToBuffer(invoiceSpec);
  return new Response(buffer, {
    headers: { "Content-Type": "application/pdf" },
  });
}

Email Generation

邮件生成

typescript
import { 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);
typescript
import { 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)

MCP集成(Claude、ChatGPT、Cursor)

typescript
import { createMCPServer } from "@json-render/mcp";

const server = createMCPServer({
  catalog,
  name: "my-ui-server",
  version: "1.0.0",
});

server.start();
typescript
import { createMCPServer } from "@json-render/mcp";

const server = createMCPServer({
  catalog,
  name: "my-ui-server",
  version: "1.0.0",
});

server.start();

State Management Integration

状态管理集成

typescript
import { 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} />;
typescript
import { create } from "zustand";
import { createZustandAdapter } from "@json-render/zustand";

const useStore = create((set) => ({
  data: {},
  setData: (data) => set({ data }),
}));

const stateStore = createZustandAdapter(useStore);

// 传递给Renderer以结合状态处理操作
<Renderer spec={spec} registry={registry} stateStore={stateStore} />;

YAML Wire Format

YAML传输格式

typescript
import { parseYAML, toYAML } from "@json-render/yaml";

// AI can output YAML instead of JSON (often more token-efficient)
const yamlSpec = `
root: card-1
elements:
  card-1:
    type: Card
    props:
      title: Hello World
    children: [button-1]
  button-1:
    type: Button
    props:
      label: Click Me
    children: []
`;

const spec = parseYAML(yamlSpec);
typescript
import { parseYAML, toYAML } from "@json-render/yaml";

// AI可以输出YAML而非JSON(通常更节省token)
const yamlSpec = `
root: card-1
elements:
  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

完整Next.js App Router示例

tsx
// app/dashboard/page.tsx
import { 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;
        }
      }}
    />
  );
}
tsx
// app/dashboard/page.tsx
import { 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 ?? "展示销售概览仪表板";

  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 role
function 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 : {},
  });
}
typescript
// 根据用户角色限制组件目录
function getCatalogForRole(role: "admin" | "viewer") {
  const base = { Card, Stack, Heading, Text, Metric };
  const adminOnly = role === "admin" ? { Button, Form, Table } : {};
  const adminActions = role === "admin"
    ? { export: { description: "导出数据" } }
    : {};

  return defineCatalog(schema, {
    components: { ...base, ...adminOnly },
    actions: adminOnly ? adminActions : {},
  });
}

Dynamic Props with Runtime Data

结合运行时数据的动态属性

tsx
// Components can fetch their own data
const { registry } = defineRegistry(catalog, {
  components: {
    LiveMetric: ({ props }) => {
      const { data } = useSWR(`/api/metrics/${props.metricId}`);
      return (
        <div>
          <span>{props.label}</span>
          <span>{data?.value ?? "..."}</span>
        </div>
      );
    },
  },
});
tsx
// 组件可自行获取数据
const { 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

类型安全的操作处理

typescript
import { 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
  }
};
typescript
import { type ActionHandler } from "@json-render/core";

const handleAction: ActionHandler<typeof catalog> = (action, payload) => {
  // action和payload会根据你的目录定义自动推导类型
  if (action === "navigate") {
    router.push(payload.path); // payload.path被推导为string类型
  }
};

Troubleshooting

故障排除

ProblemCauseFix
AI generates unknown component typeComponent not in catalogAdd component to
defineCatalog
or update AI prompt
Props validation errorAI hallucinated a propTighten 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 incorrectlyStreaming not handledUse
parseSpecStream
utility or check for null elements before render
Actions not firing
onAction
not passed to
Renderer
Pass
onAction
prop to
<Renderer>
shadcn components unstyledMissing Tailwind configEnsure
@json-render/shadcn
paths are in
tailwind.config.js
content array
TypeScript errors in registryCatalog/registry mismatchEnsure
defineRegistry(catalog, ...)
uses the same
catalog
instance
问题原因解决方法
AI生成了未知的组件类型组件未在目录中定义将组件添加到
defineCatalog
或更新AI提示
属性验证错误AI生成了不存在的属性收紧Zod schema,添加
.strict()
.describe()
提示
渲染器无内容显示
root
键与
elements
中的键不匹配
检查规格结构;root必须引用有效的元素ID
部分规格渲染异常未处理流式传输使用
parseSpecStream
工具或在渲染前检查空元素
操作未触发未向
Renderer
传递
onAction
<Renderer>
传递
onAction
属性
shadcn组件无样式缺少Tailwind配置确保
@json-render/shadcn
路径在
tailwind.config.js
的content数组中
注册表出现TypeScript错误目录与注册表不匹配确保
defineRegistry(catalog, ...)
使用同一个
catalog
实例

Environment Variables

环境变量

bash
undefined
bash
undefined

For AI generation (use your preferred provider)

AI生成相关(使用你偏好的服务商)

OPENAI_API_KEY=your_key_here ANTHROPIC_API_KEY=your_key_here
OPENAI_API_KEY=你的密钥 ANTHROPIC_API_KEY=你的密钥

For MCP server

MCP服务器相关

MCP_SERVER_PORT=3001
undefined
MCP_SERVER_PORT=3001
undefined

Key API Reference

核心API参考

typescript
// Core
defineCatalog(schema, { components, actions })  // Define guardrails
getCatalogSchema(catalog)                        // Get Zod schema for AI
getCatalogPrompt(catalog)                        // Get system prompt for AI

// React
defineRegistry(catalog, { components })          // Create typed registry
<Renderer spec={spec} registry={registry} onAction={fn} />

// Core utilities
parseSpecStream(stream)    // Parse streaming partial specs
toYAML(spec)              // Convert spec to YAML
parseYAML(yaml)           // Parse YAML spec to JSON

// PDF
renderToBuffer(spec)       // → Buffer
renderToStream(spec)       // → ReadableStream

// Email
renderToHtml(spec)         // → HTML string
renderToText(spec)         // → plain text string
typescript
// 核心API
defineCatalog(schema, { components, actions })  // 定义约束规则
getCatalogSchema(catalog)                        // 获取用于AI的Zod schema
getCatalogPrompt(catalog)                        // 获取用于AI的系统提示词

// React相关API
defineRegistry(catalog, { components })          // 创建类型化注册表
<Renderer spec={spec} registry={registry} onAction={fn} />

// 核心工具函数
parseSpecStream(stream)    // 解析流式传输的部分规格
toYAML(spec)              // 将规格转换为YAML
parseYAML(yaml)           // 将YAML规格解析为JSON

// PDF相关API
renderToBuffer(spec)       // → Buffer
renderToStream(spec)       // → ReadableStream

// 邮件相关API
renderToHtml(spec)         // → HTML字符串
renderToText(spec)         // → 纯文本字符串