json-render-generative-ui
生成式 UI 框架,通过自然语言生成动态界面,AI 输出 JSON 自动渲染预定义组件,快速构建用户界面
npx skills add aradotso/trending-skills --skill json-render-generative-uiBefore / After 效果对比
1 组手动编写 JSX、配置样式、处理状态管理,构建一个表单页面需要 2-3 小时,修改需求还要重写代码
描述需求自动生成 UI 组件 JSON,直接渲染可用界面,10 分钟完成复杂表单,修改只需调整描述
description SKILL.md
json-render-generative-ui
json-render Generative UI Framework
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.
Installation
# React (core)
npm install @json-render/core @json-render/react
# React + shadcn/ui (36 pre-built components)
npm install @json-render/shadcn
# React Native
npm install @json-render/core @json-render/react-native
# Vue
npm install @json-render/core @json-render/vue
# Svelte
npm install @json-render/core @json-render/svelte
# SolidJS
npm install @json-render/core @json-render/solid
# Video (Remotion)
npm install @json-render/core @json-render/remotion
# 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
# 3D (React Three Fiber)
npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three
# OG Images / SVG / PNG
npm install @json-render/core @json-render/image
# State management adapters
npm install @json-render/zustand # or redux, jotai, xstate
# MCP integration (Claude, ChatGPT, Cursor)
npm install @json-render/mcp
# YAML wire format
npm 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:
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
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() }),
},
},
});
Step 2: Define a Registry (React)
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
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);
}}
/>
);
}
Generating Specs with AI (Vercel AI SDK)
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"
);
Streaming Specs
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} />;
}
Using Pre-built shadcn/ui Components
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} />;
}
Vue Renderer
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>
React Native Renderer
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} />;
}
PDF Generation
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" },
});
}
Email Generation
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)
import { createMCPServer } from "@json-render/mcp";
const server = createMCPServer({
catalog,
name: "my-ui-server",
version: "1.0.0",
});
server.start();
State Management Integration
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} />;
YAML Wire Format
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);
Full Next.js App Router Example
// 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} />;
}
// 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
// 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 : {},
});
}
Dynamic Props with Runtime Data
// 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>
);
},
},
});
Type-Safe Action Handling
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
}
};
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
# For AI generation (use your preferred provider)
OPENAI_API_KEY=your_key_here
ANTHROPIC_API_KEY=your_key_here
# For MCP server
MCP_SERVER_PORT=3001
Key API Reference
// 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
Weekly Installs251Repositoryaradotso/trending-skillsGitHub Stars8First Seen5 days agoSecurity AuditsGen Agent Trust HubPassSocketPassSnykPassInstalled ongithub-copilot250codex250amp250cline250kimi-cli250gemini-cli250
forum用户评价 (0)
发表评价
暂无评价,来写第一条吧
统计数据
用户评分
为此 Skill 评分