Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

DreamRockSmartTech/json-render

Open more actions menu
 
 

Repository files navigation

json-render

The Generative UI framework.

Generate dynamic, personalized UIs from prompts without sacrificing reliability. Predefined components and actions for safe, predictable output.

# for React
npm install @json-render/core @json-render/react
# for React with pre-built shadcn/ui components
npm install @json-render/shadcn
# or for React Native
npm install @json-render/core @json-render/react-native
# or for video
npm install @json-render/core @json-render/remotion
# or for PDF documents
npm install @json-render/core @json-render/react-pdf
# or for HTML email
npm install @json-render/core @json-render/react-email @react-email/components @react-email/render
# or for Vue
npm install @json-render/core @json-render/vue
# or for Svelte
npm install @json-render/core @json-render/svelte
# or for SolidJS
npm install @json-render/core @json-render/solid
# or for terminal UIs
npm install @json-render/core @json-render/ink ink react
# or for full Next.js apps (routes, layouts, SSR, metadata)
npm install @json-render/core @json-render/react @json-render/next
# or for 3D scenes (and gaussian splatting via the GaussianSplat component)
npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three

Why json-render?

json-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:

  • Guardrailed - AI can only use components in your catalog
  • Predictable - JSON output matches your schema, every time
  • Fast - Stream and render progressively as the model responds
  • Cross-Platform - React, Vue, Svelte, Solid (web), React Native (mobile) from the same catalog
  • Batteries Included - 36 pre-built shadcn/ui components ready to use

Quick Start

1. Define Your 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",
    },
    Metric: {
      props: z.object({
        label: z.string(),
        value: z.string(),
        format: z.enum(["currency", "percent", "number"]).nullable(),
      }),
      description: "Display a metric value",
    },
    Button: {
      props: z.object({
        label: z.string(),
        action: z.string(),
      }),
      description: "Clickable button",
    },
  },
  actions: {
    export_report: { description: "Export dashboard to PDF" },
    refresh_data: { description: "Refresh all metrics" },
  },
});

2. Define Your Components

import { defineRegistry, Renderer } from "@json-render/react";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: ({ props, children }) => (
      <div className="card">
        <h3>{props.title}</h3>
        {children}
      </div>
    ),
    Metric: ({ props }) => (
      <div className="metric">
        <span>{props.label}</span>
        <span>{format(props.value, props.format)}</span>
      </div>
    ),
    Button: ({ props, emit }) => (
      <button onClick={() => emit("press")}>{props.label}</button>
    ),
  },
});

3. Render AI-Generated Specs

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

That's it. AI generates JSON, you render it safely.


Packages

Package Description
@json-render/core Schemas, catalogs, AI prompts, dynamic props, SpecStream utilities
@json-render/react React renderer, contexts, hooks
@json-render/vue Vue 3 renderer, composables, providers
@json-render/svelte Svelte 5 renderer with runes-based reactivity
@json-render/solid SolidJS renderer with fine-grained reactive contexts
@json-render/shadcn 36 pre-built shadcn/ui components (Radix UI + Tailwind CSS)
@json-render/shadcn-svelte 36 pre-built shadcn-svelte components (Svelte 5 + Tailwind CSS)
@json-render/react-three-fiber React Three Fiber renderer for 3D scenes (20 built-in components, including GaussianSplat)
@json-render/react-native React Native renderer with standard mobile components
@json-render/next Next.js renderer — JSON becomes full apps with routes, layouts, SSR
@json-render/remotion Remotion video renderer, timeline schema
@json-render/react-pdf React PDF renderer for generating PDF documents from specs
@json-render/react-email React Email renderer for HTML/plain-text emails from specs
@json-render/ink Ink terminal renderer with built-in components for interactive TUIs.
@json-render/image Image renderer for SVG/PNG output (OG images, social cards) via Satori
@json-render/directives Pre-built custom directives — $format, $math, $concat, $count, $truncate, $pluralize, $join, $t (i18n)
@json-render/codegen Utilities for generating code from json-render UI trees
@json-render/devtools Framework-agnostic devtools core — panel UI, event store, picker, stream taps
@json-render/devtools-react React adapter for @json-render/devtools (drop-in <JsonRenderDevtools />)
@json-render/devtools-vue Vue adapter for @json-render/devtools
@json-render/devtools-svelte Svelte adapter for @json-render/devtools
@json-render/devtools-solid SolidJS adapter for @json-render/devtools
@json-render/redux Redux / Redux Toolkit adapter for StateStore
@json-render/zustand Zustand adapter for StateStore
@json-render/jotai Jotai adapter for StateStore
@json-render/xstate XState Store (atom) adapter for StateStore
@json-render/mcp MCP Apps integration for Claude, ChatGPT, Cursor, VS Code
@json-render/yaml YAML wire format with streaming parser, edit modes, AI SDK transform

Renderers

React (UI)

import { defineRegistry, Renderer } from "@json-render/react";
import { schema } from "@json-render/react/schema";

// Flat spec format (root key + elements map)
const spec = {
  root: "card-1",
  elements: {
    "card-1": {
      type: "Card",
      props: { title: "Hello" },
      children: ["button-1"],
    },
    "button-1": {
      type: "Button",
      props: { label: "Click me" },
      children: [],
    },
  },
};

// defineRegistry creates a type-safe component registry
const { registry } = defineRegistry(catalog, { components });
<Renderer spec={spec} registry={registry} />;

Vue (UI)

import { h } from "vue";
import { defineRegistry, Renderer } from "@json-render/vue";
import { schema } from "@json-render/vue/schema";

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("press") }, props.label),
  },
});

// In your Vue component template:
// <Renderer :spec="spec" :registry="registry" />

Svelte (UI)

import { defineRegistry, Renderer } from "@json-render/svelte";
import { schema } from "@json-render/svelte/schema";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: ({ props, children }) => /* Svelte 5 snippet */,
    Button: ({ props, emit }) => /* Svelte 5 snippet */,
  },
});

// In your Svelte component:
// <Renderer spec={spec} registry={registry} />

Solid (UI)

import { defineRegistry, Renderer } from "@json-render/solid";
import { schema } from "@json-render/solid/schema";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: (renderProps) => <div>{renderProps.children}</div>,
    Button: (renderProps) => (
      <button onClick={() => renderProps.emit("press")}>
        {renderProps.element.props.label as string}
      </button>
    ),
  },
});

<Renderer spec={spec} registry={registry} />;

shadcn/ui (Web)

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 components from the 36 standard definitions
const catalog = defineCatalog(schema, {
  components: {
    Card: shadcnComponentDefinitions.Card,
    Stack: shadcnComponentDefinitions.Stack,
    Heading: shadcnComponentDefinitions.Heading,
    Button: shadcnComponentDefinitions.Button,
  },
  actions: {},
});

// Use matching implementations
const { registry } = defineRegistry(catalog, {
  components: {
    Card: shadcnComponents.Card,
    Stack: shadcnComponents.Stack,
    Heading: shadcnComponents.Heading,
    Button: shadcnComponents.Button,
  },
});

<Renderer spec={spec} registry={registry} />;

React Native (Mobile)

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 components included
const catalog = defineCatalog(schema, {
  components: { ...standardComponentDefinitions },
  actions: standardActionDefinitions,
});

const { registry } = defineRegistry(catalog, { components: {} });
<Renderer spec={spec} registry={registry} />;

Remotion (Video)

import { Player } from "@remotion/player";
import {
  Renderer,
  schema,
  standardComponentDefinitions,
} from "@json-render/remotion";

// Timeline spec format
const spec = {
  composition: {
    id: "video",
    fps: 30,
    width: 1920,
    height: 1080,
    durationInFrames: 300,
  },
  tracks: [{ id: "main", name: "Main", type: "video", enabled: true }],
  clips: [
    {
      id: "clip-1",
      trackId: "main",
      component: "TitleCard",
      props: { title: "Hello" },
      from: 0,
      durationInFrames: 90,
    },
  ],
  audio: { tracks: [] },
};

<Player
  component={Renderer}
  inputProps={{ spec }}
  durationInFrames={spec.composition.durationInFrames}
  fps={spec.composition.fps}
  compositionWidth={spec.composition.width}
  compositionHeight={spec.composition.height}
/>;

React PDF (Documents)

import { renderToBuffer } from "@json-render/react-pdf";

const spec = {
  root: "doc",
  elements: {
    doc: {
      type: "Document",
      props: { title: "Invoice" },
      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: "Price", width: "40%", align: "right" },
        ],
        rows: [
          ["Widget A", "$10.00"],
          ["Widget B", "$25.00"],
        ],
      },
      children: [],
    },
  },
};

// Render to buffer, stream, or file
const buffer = await renderToBuffer(spec);

React Email (Email)

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 spec = {
  root: "html-1",
  elements: {
    "html-1": {
      type: "Html",
      props: { lang: "en", dir: "ltr" },
      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", padding: "20px" },
      },
      children: ["heading-1", "text-1"],
    },
    "heading-1": { type: "Heading", props: { text: "Welcome" }, children: [] },
    "text-1": {
      type: "Text",
      props: { text: "Thanks for signing up." },
      children: [],
    },
  },
};

const html = await renderToHtml(spec);

Image (SVG/PNG)

import { renderToPng } from "@json-render/image/render";

const spec = {
  root: "frame",
  elements: {
    frame: {
      type: "Frame",
      props: { width: 1200, height: 630, backgroundColor: "#1a1a2e" },
      children: ["heading"],
    },
    heading: {
      type: "Heading",
      props: { text: "Hello World", level: "h1", color: "#ffffff" },
      children: [],
    },
  },
};

// Render to PNG (requires @resvg/resvg-js)
const png = await renderToPng(spec, { fonts });

// Or render to SVG string
import { renderToSvg } from "@json-render/image/render";
const svg = await renderToSvg(spec, { fonts });

Three.js (3D)

import { defineCatalog } from "@json-render/core";
import { schema, defineRegistry } from "@json-render/react";
import {
  threeComponentDefinitions,
  threeComponents,
  ThreeCanvas,
} from "@json-render/react-three-fiber";

const catalog = defineCatalog(schema, {
  components: {
    Box: threeComponentDefinitions.Box,
    Sphere: threeComponentDefinitions.Sphere,
    AmbientLight: threeComponentDefinitions.AmbientLight,
    DirectionalLight: threeComponentDefinitions.DirectionalLight,
    GaussianSplat: threeComponentDefinitions.GaussianSplat,
    OrbitControls: threeComponentDefinitions.OrbitControls,
  },
  actions: {},
});

const { registry } = defineRegistry(catalog, {
  components: {
    Box: threeComponents.Box,
    Sphere: threeComponents.Sphere,
    AmbientLight: threeComponents.AmbientLight,
    DirectionalLight: threeComponents.DirectionalLight,
    GaussianSplat: threeComponents.GaussianSplat,
    OrbitControls: threeComponents.OrbitControls,
  },
});

<ThreeCanvas
  spec={spec}
  registry={registry}
  shadows
  camera={{ position: [5, 5, 5], fov: 50 }}
  style={{ width: "100%", height: "100vh" }}
/>;

Next.js (Full Apps)

import type { NextAppSpec } from "@json-render/next";
import { createNextApp } from "@json-render/next/server";
import { NextAppProvider } from "@json-render/next";

const spec: NextAppSpec = {
  metadata: { title: { default: "My App", template: "%s | My App" } },
  layouts: {
    main: {
      root: "shell",
      elements: {
        shell: { type: "Container", props: {}, children: ["nav", "slot"] },
        nav: { type: "NavBar", props: {}, children: [] },
        slot: { type: "Slot", props: {}, children: [] },
      },
    },
  },
  routes: {
    "/": {
      layout: "main",
      metadata: { title: "Home" },
      page: {
        root: "hero",
        elements: {
          hero: { type: "Card", props: { title: "Welcome" }, children: [] },
        },
      },
    },
  },
};

// Server: creates Page, generateMetadata, generateStaticParams
const app = createNextApp({ spec });

// Client: wrap your layout with NextAppProvider
// <NextAppProvider registry={registry} handlers={handlers}>
//   {children}
// </NextAppProvider>

shadcn-svelte (Svelte)

import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/svelte/schema";
import { defineRegistry, Renderer } from "@json-render/svelte";
import { shadcnComponentDefinitions } from "@json-render/shadcn-svelte/catalog";
import { shadcnComponents } from "@json-render/shadcn-svelte";

const catalog = defineCatalog(schema, {
  components: {
    Card: shadcnComponentDefinitions.Card,
    Stack: shadcnComponentDefinitions.Stack,
    Heading: shadcnComponentDefinitions.Heading,
    Button: shadcnComponentDefinitions.Button,
  },
  actions: {},
});

const { registry } = defineRegistry(catalog, {
  components: {
    Card: shadcnComponents.Card,
    Stack: shadcnComponents.Stack,
    Heading: shadcnComponents.Heading,
    Button: shadcnComponents.Button,
  },
});

// In your Svelte component:
// <Renderer spec={spec} registry={registry} />

Devtools

Drop-in inspector panel for any json-render app. Spec tree, state editor, action log, stream log, catalog browser, DOM picker.

// React
import { JsonRenderDevtools } from "@json-render/devtools-react";

<JSONUIProvider registry={registry} handlers={handlers}>
  <Renderer spec={spec} registry={registry} />
  <JsonRenderDevtools spec={spec} catalog={catalog} messages={messages} />
</JSONUIProvider>;

Floating toggle appears bottom-right. Hotkey: Ctrl/Cmd + Shift + J. Tree-shakes to null in production.

Available for React, Vue, Svelte, and Solid — swap @json-render/devtools-react for the adapter that matches your renderer.

Ink (Terminal)

import { defineCatalog } from "@json-render/core";
import {
  schema,
  standardComponentDefinitions,
  standardActionDefinitions,
  defineRegistry,
  Renderer,
  JSONUIProvider,
} from "@json-render/ink";

const catalog = defineCatalog(schema, {
  components: { ...standardComponentDefinitions },
  actions: standardActionDefinitions,
});

const { registry } = defineRegistry(catalog, { components: {} });

const spec = {
  root: "card-1",
  elements: {
    "card-1": {
      type: "Card",
      props: { title: "Status" },
      children: ["status-1"],
    },
    "status-1": {
      type: "StatusLine",
      props: { label: "Build", status: "success" },
      children: [],
    },
  },
};

<JSONUIProvider initialState={{}}>
  <Renderer spec={spec} registry={registry} />
</JSONUIProvider>;

Features

Streaming (SpecStream)

Stream AI responses progressively:

import { createSpecStreamCompiler } from "@json-render/core";

const compiler = createSpecStreamCompiler<MySpec>();

// Process chunks as they arrive
const { result, newPatches } = compiler.push(chunk);
setSpec(result); // Update UI with partial result

// Get final result
const finalSpec = compiler.getResult();

AI Prompt Generation

Generate system prompts from your catalog:

const systemPrompt = catalog.prompt();
// Includes component descriptions, props schemas, available actions

Conditional Visibility

{
  "type": "Alert",
  "props": { "message": "Error occurred" },
  "visible": [
    { "$state": "/form/hasError" },
    { "$state": "/form/errorDismissed", "not": true }
  ]
}

Dynamic Props

Any prop value can be data-driven using expressions:

{
  "type": "Icon",
  "props": {
    "name": {
      "$cond": { "$state": "/activeTab", "eq": "home" },
      "$then": "home",
      "$else": "home-outline"
    },
    "color": {
      "$cond": { "$state": "/activeTab", "eq": "home" },
      "$then": "#007AFF",
      "$else": "#8E8E93"
    }
  }
}

Expression forms:

  • { "$state": "/state/key" } - reads a value from the state model
  • { "$cond": <condition>, "$then": <value>, "$else": <value> } - evaluates a condition and picks a branch
  • { "$template": "Hello, ${/user/name}!" } - interpolates state values into strings
  • { "$computed": "fn", "args": { ... } } - calls a registered function with resolved args

Actions

Components can trigger actions, including the built-in setState action:

{
  "type": "Pressable",
  "props": {
    "action": "setState",
    "actionParams": { "statePath": "/activeTab", "value": "home" }
  },
  "children": ["home-icon"]
}

The setState action updates the state model directly, which re-evaluates visibility conditions and dynamic prop expressions.

State Watchers

React to state changes by triggering actions:

{
  "type": "Select",
  "props": {
    "value": { "$bindState": "/form/country" },
    "options": ["US", "Canada", "UK"]
  },
  "watch": {
    "/form/country": {
      "action": "loadCities",
      "params": { "country": { "$state": "/form/country" } }
    }
  }
}

watch is a top-level field on elements (sibling of type/props/children). Watchers fire when the watched value changes, not on initial render.


Demo

git clone https://github.com/vercel-labs/json-render
cd json-render
pnpm install
pnpm dev

How It Works

flowchart LR
    A[User Prompt] --> B[AI + Catalog]
    B --> C[JSON Spec]
    C --> D[Renderer]

    B -.- E([guardrailed])
    C -.- F([predictable])
    D -.- G([streamed])
Loading
  1. Define the guardrails - what components, actions, and data bindings AI can use
  2. Prompt - describe what you want in natural language
  3. AI generates JSON - output is always predictable, constrained to your catalog
  4. Render fast - stream and render progressively as the model responds

License

Apache-2.0

About

The Generative UI framework

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 80.6%
  • MDX 12.6%
  • Svelte 5.8%
  • Other 1.0%
Morty Proxy This is a proxified and sanitized view of the page, visit original site.