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
npm install @json-render/core @json-render/react-pdf
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: // //
// 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
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