PluginBench
Skill
Review
Audit score 70

json-render-generative-ui

reason-machines/trending-skills

Render AI-generated JSON specs into type-safe UI components across React, Vue, Svelte, Solid, React Native, video, PDF, and email.

What is json-render-generative-ui?

json-render is a Generative UI framework that constrains AI-generated interfaces to a predefined component catalog, outputting JSON that renders safely and predictably across platforms. Use it when you need AI to generate dynamic UIs from natural language prompts while maintaining type safety and control.

  • Define a catalog of allowed components and actions to constrain AI output
  • Generate JSON specs from natural language prompts using AI models
  • Render specs as type-safe UI components across multiple platforms (React, Vue, Svelte, Solid, React Native, video, PDF, email)
  • Stream partial specs for real-time UI generation
  • Support 36 pre-built shadcn/ui components out of the box
  • Trigger named actions from rendered components back to your application

How to install json-render-generative-ui

npx skills add https://github.com/reason-machines/trending-skills --skill json-render-generative-ui
Prerequisites
  • Node.js and npm/yarn
  • An AI model integration (OpenAI, Anthropic, etc.) via Vercel AI SDK or similar
  • For shadcn/ui components: existing shadcn/ui setup in your project
Claude Code
Cursor
Windsurf
Cline

How to use json-render-generative-ui

  1. 1.Install the appropriate @json-render packages for your platform (e.g., @json-render/react for React)
  2. 2.Define a catalog using defineCatalog() specifying allowed components, their props schemas, and available actions
  3. 3.Define a registry using defineRegistry() that maps catalog components to actual UI implementations
  4. 4.Use generateObject() or streamObject() from the Vercel AI SDK to generate specs from user prompts
  5. 5.Render the spec using the Renderer component, passing the spec and registry
  6. 6.Handle action events via the onAction callback to respond to user interactions

Use cases

Good for
  • Generate sales dashboards from text prompts with metrics and export buttons
  • Create dynamic forms and data-entry UIs constrained to predefined fields
  • Build real-time streaming UI updates as AI generates component specs
  • Generate email templates and PDFs from AI-generated JSON specs
  • Create video or image content dynamically from AI-generated component descriptions
Who it's for
  • Full-stack developers building AI-powered applications
  • Product teams needing dynamic UI generation from LLM outputs
  • Teams using Claude, ChatGPT, or Cursor with MCP integration
  • Developers working across multiple UI frameworks (React, Vue, Svelte, etc.)

json-render-generative-ui FAQ

What platforms does json-render support?

React, Vue, Svelte, SolidJS, React Native, Remotion (video), react-pdf (PDF), react-email (email), React Three Fiber (3D), and image/SVG/PNG generation.

How does json-render keep AI output safe?

You define a catalog that constrains which components and actions AI can use. AI generates JSON specs that conform to your catalog schema, so only predefined components with validated props can be rendered.

Can I use my own custom components?

Yes. You define the catalog with your custom components and their prop schemas, then implement them in the registry for your target platform.

Does json-render support streaming UI generation?

Yes. Use streamObject() from the Vercel AI SDK to stream partial specs, and the Renderer handles partial specs gracefully for real-time UI updates.

What is the MCP integration for?

The @json-render/mcp package enables json-render to work as a tool with Claude, ChatGPT, and Cursor, allowing these agents to generate and render UIs directly.

Full instructions (SKILL.md)

Source of truth, from reason-machines/trending-skills.


name: json-render-generative-ui description: 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 — 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

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)

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

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 nothingroot key doesn't match an elements keyCheck 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 firingonAction not passed to RendererPass 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

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