Copy for LLM View Markdown Build your own schema and renderer with @json-render/core.
@json-render/core is schema-agnostic. While @json-render/react provides a ready-to-use schema and renderer, you can create your own to match any JSON structure - whether it's a domain-specific format, an existing protocol, or something entirely custom.
Start by defining the JSON structure your system will use. Here's an example of a simple dashboard schema:
{
" layout " : " grid " ,
" columns " : 2 ,
" widgets " : [
{
" type " : " metric " ,
" title " : " Revenue " ,
" value " : " $12,345 " ,
" trend " : " up "
},
{
" type " : " chart " ,
" title " : " Sales " ,
" chartType " : " line " ,
" dataKey " : " salesData "
},
{
" type " : " table " ,
" title " : " Recent Orders " ,
" columns " : [ " id " , " customer " , " amount " ],
" dataKey " : " orders "
}
]
}
Define a catalog that describes your components and validates props using defineCatalog — see Catalog .
import { defineCatalog } from ' @json-render/core ' ;
import { z } from ' zod ' ;
export const dashboardCatalog = defineCatalog (mySchema , {
components : {
metric : {
description : ' Displays a single metric value ' ,
props : z . object ( {
title : z . string () ,
value : z . string () ,
trend : z . enum ([ ' up ' , ' down ' , ' flat ' ]) . optional () ,
change : z . string () . optional () ,
} ) ,
},
chart : {
description : ' Renders a chart visualization ' ,
props : z . object ( {
title : z . string () ,
chartType : z . enum ([ ' line ' , ' bar ' , ' pie ' , ' area ' ]) ,
dataKey : z . string () ,
height : z . number () . optional () ,
} ) ,
},
table : {
description : ' Displays tabular data ' ,
props : z . object ( {
title : z . string () ,
columns : z . array (z . string ()) ,
dataKey : z . string () ,
pageSize : z . number () . optional () ,
} ) ,
},
text : {
description : ' Displays text content ' ,
props : z . object ( {
content : z . string () ,
variant : z . enum ([ ' heading ' , ' body ' , ' caption ' ]) . optional () ,
} ) ,
},
},
} ) ;
Create a schema for the overall document structure:
import { z } from ' zod ' ;
const WidgetSchema = z . object ( {
type : z . string () ,
title : z . string () . optional () ,
// Additional props validated by catalog
} ) . passthrough () ;
export const DashboardSchema = z . object ( {
layout : z . enum ([ ' grid ' , ' stack ' , ' tabs ' ]) ,
columns : z . number () . optional () ,
widgets : z . array (WidgetSchema) ,
} ) ;
export type Dashboard = z . infer < typeof DashboardSchema >;
export type Widget = z . infer < typeof WidgetSchema >;
Create a renderer that maps your schema to React components:
import React from ' react ' ;
import { dashboardCatalog } from ' ./catalog ' ;
import type { Dashboard , Widget } from ' ./schema ' ;
// Widget component registry
const widgetComponents : Record < string , React . FC < any >> = {
metric : ({ title , value , trend , change }) => (
< div className = " p-4 rounded-lg border " >
< p className = " text-sm text-muted-foreground " > { title } </ p >
< p className = " text-2xl font-bold " > { value } </ p >
{ trend && (
< p className = { ` text-sm ${ trend === ' up ' ? ' text-green-500 ' : ' text-red-500 '}` } >
{ trend === ' up ' ? ' + ' : ' - ' }{ change }
</ p >
) }
</ div >
) ,
chart : ({ title , chartType , data }) => (
< div className = " p-4 rounded-lg border " >
< p className = " font-medium mb-2 " > { title } </ p >
< div className = " h-48 bg-muted rounded flex items-center justify-center " >
{ /* Your chart library here */ }
< span className = " text-muted-foreground " > { chartType } chart</ span >
</ div >
</ div >
) ,
table : ({ title , columns , data }) => (
< div className = " p-4 rounded-lg border " >
< p className = " font-medium mb-2 " > { title } </ p >
< table className = " w-full text-sm " >
< thead >
< tr >
{ columns . map ( ( col : string ) => (
< th key = { col } className = " text-left p-2 border-b " > { col } </ th >
)) }
</ tr >
</ thead >
< tbody >
{ data ?. map ( ( row : any , i : number ) => (
< tr key = { i } >
{ columns . map ( ( col : string ) => (
< td key = { col } className = " p-2 border-b " > { row[col] } </ td >
)) }
</ tr >
)) }
</ tbody >
</ table >
</ div >
) ,
text : ({ content , variant = ' body ' }) => {
const className = {
heading : ' text-xl font-bold ' ,
body : ' text-base ' ,
caption : ' text-sm text-muted-foreground ' ,
} [variant] ;
return < p className = { className } > { content } </ p > ;
},
};
// Main renderer
export function DashboardRenderer ({
spec ,
data = {},
} : {
spec : Dashboard ;
data ?: Record < string , any >;
}) {
const layoutClass = {
grid : ` grid gap-4 ${ spec . columns ? ` grid-cols- ${ spec . columns }` : ' grid-cols-2 '}` ,
stack : ' flex flex-col gap-4 ' ,
tabs : ' space-y-4 ' ,
} [spec . layout] ;
return (
< div className = { layoutClass } >
{ spec . widgets . map ( ( widget , index ) => {
const Component = widgetComponents[widget . type] ;
if ( ! Component) {
console . warn ( ` Unknown widget type: ${ widget . type }` ) ;
return null ;
}
// Resolve data references
const widgetData = widget . dataKey ? data[widget . dataKey] : undefined ;
return (
< Component
key = { index }
{ ... widget }
data = { widgetData }
/>
) ;
} ) }
</ div >
) ;
}
Use the catalog to generate system prompts for AI:
const systemPrompt = dashboardCatalog . prompt ( {
customRules : [
' Use metric widgets for single KPI values ' ,
' Use chart widgets for time-series data ' ,
' Use table widgets for lists of records ' ,
' Limit dashboards to 6 widgets maximum ' ,
] ,
} ) ;
// Use with any LLM
const response = await generateText ( {
model : ' gpt-4 ' ,
system : systemPrompt ,
prompt : ' Create a sales dashboard with revenue, orders, and a chart ' ,
} ) ;
Validate incoming specs against your schema. Use catalog.validate() to check AI output against the catalog's Zod schema:
function validateDashboard ( spec : unknown ) {
// Validate root structure
const rootResult = DashboardSchema . safeParse (spec) ;
if ( ! rootResult . success) {
return { valid : false , errors : rootResult . error . errors };
}
// Validate each widget's props against the catalog
const result = dashboardCatalog . validate (spec) ;
if ( ! result . success) {
return { valid : false , errors : result . error . errors };
}
return { valid : true , errors : [] };
}
' use client ' ;
import { useState } from ' react ' ;
import { DashboardRenderer } from ' ./renderer ' ;
import type { Dashboard } from ' ./schema ' ;
const initialSpec : Dashboard = {
layout : ' grid ' ,
columns : 2 ,
widgets : [
{ type : ' metric ' , title : ' Revenue ' , value : ' $12,345 ' , trend : ' up ' },
{ type : ' metric ' , title : ' Orders ' , value : ' 156 ' , trend : ' up ' },
{ type : ' chart ' , title : ' Sales Trend ' , chartType : ' line ' , dataKey : ' sales ' },
{ type : ' table ' , title : ' Recent Orders ' , columns : [ ' id ' , ' customer ' , ' amount ' ] , dataKey : ' orders ' },
] ,
};
const data = {
sales : [ /* chart data */ ] ,
orders : [
{ id : ' 001 ' , customer : ' Acme Inc ' , amount : ' $500 ' },
{ id : ' 002 ' , customer : ' Globex ' , amount : ' $750 ' },
] ,
};
export function MyDashboard () {
const [ spec , setSpec ] = useState (initialSpec) ;
return < DashboardRenderer spec = { spec } data = { data } /> ;
}
See how to integrate with A2UI or Adaptive Cards protocols.