JSON-Render: Guarded AI‑Prompted UI Library for React

JSON‑Render: Guarded AI‑Prompted UI Library for React

In the age of large language models, developers are constantly looking for ways to turn pure text prompts into actionable user interfaces. JSON‑Render fills that niche by giving AI a predictable and safe vocabulary so that the output is always a JSON tree you can render with React — no surprises, no security holes.

What is JSON‑Render?

JSON‑Render is a lightweight, TypeScript‑first toolkit consisting of two NPM packages:

  • @json-render/core – schema definitions, guardrails, visibility logic, and action handlers.
  • @json-render/react – React components, hooks, and renderers that turn the JSON into a live UI.

Key benefits:

Feature Why it matters
Guardrailed The AI is restricted to a catalog of allowed component types, preventing malicious code injection.
Predictable The JSON follows a Zod schema. Every property can be type‑checked at run time.
Fast The stream can be rendered as the model sends data, meaning users see updates in real time.
Composable Combine your own React components with guardrails; the library is agnostic to styling.

Quick Start Guide

Below is a step‑by‑step walkthrough of a minimal example. Assume a fresh Node project with pnpm.

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

The dev command launches four services: * localhost:3000 – Docs & Playground * localhost:3001 – Example dashboard app * Websocket endpoints for the API * A local ChatGPT‑compatible backend (see the repo for custom LLM integration)

1. Define the Catalog

In packages/core/src/catalog.ts, you declare every component the AI can use. Here’s a compact example:

import { createCatalog } from '@json-render/core'
import { z } from 'zod'

const catalog = createCatalog({
  components: {
    Card: {
      props: z.object({ title: z.string() }),
      hasChildren: true,
    },
    Metric: {
      props: z.object({
        label: z.string(),
        valuePath: z.string(),
        format: z.enum(['currency', 'percent', 'number']),
      }),
    },
    Button: {
      props: z.object({ label: z.string(), action: ActionSchema }),
    },
  },
  actions: {
    export_report: { description: 'Export dashboard to PDF' },
    refresh_data: { description: 'Refresh all metrics' },
  },
})
export default catalog

ActionSchema is a Zod schema you import from @json-render/core that validates the shape of an action payload.

2. Register React Renderers

Create a small registry that maps component names to actual React elements.

const registry = {
  Card: ({ element, children }) => (
    <div className="card">
      <h3>{element.props.title}</h3>
      {children}
    </div>
  ),
  Metric: ({ element }) => {
    const value = useDataValue(element.props.valuePath)
    return <div className="metric">{format(value)}</div>
  },
  Button: ({ element, onAction }) => (
    <button onClick={() => onAction(element.props.action)}>
      {element.props.label}
    </button>
  ),
}

3. Hook It All Together

import { useUIStream, DataProvider, ActionProvider, Renderer } from '@json-render/react'

function Dashboard() {
  const { tree, send } = useUIStream({ api: '/api/generate' })

  return (
    <DataProvider initialData={{ revenue: 125000, growth: 0.15 }}>
      <ActionProvider actions={{
        export_report: () => downloadPDF(),
        refresh_data: () => refetch(),
      }}>
        <input
          placeholder="Create a revenue dashboard…"
          onKeyDown={(e) => e.key === 'Enter' && send(e.target.value)}
        />
        <Renderer tree={tree} components={registry} />
      </ActionProvider>
    </DataProvider>
  )
}

When a user types a natural‑language prompt and hits Enter, the library streams the AI’s JSON response, the registry renders it, and any defined actions trigger callback functions.

Advanced Features

Conditional Visibility

Show or hide elements based on data, authentication, or custom logic:

{
  "type": "Alert",
  "props": { "message": "Error occurred" },
  "visible": { "and": [ { "path": "/form/hasError" }, { "not": { "path": "/form/errorDismissed" } } ] }
}

Rich Actions with Confirmation

{
  "type": "Button",
  "props": {
    "label": "Refund Payment",
    "action": {
      "name": "refund",
      "params": { "paymentId": { "path": "/selected/id" } },
      "confirm": { "title": "Confirm Refund", "variant": "danger" }
    }
  }
}

Built‑in Validation

{
  "type": "TextField",
  "props": {
    "label": "Email",
    "valuePath": "/form/email",
    "checks": [
      { "fn": "required", "message": "Email is required" },
      { "fn": "email", "message": "Invalid email" }
    ],
    "validateOn": "blur"
  }
}

When to Use JSON‑Render

  • Rapid prototyping – Turn prompts into functional dashboards in minutes.
  • Decoupled UI generation – Separate your design system from the LLM.
  • Secure UI rendering – Guardrails prevent arbitrary component injection.
  • Multiplatform – The core can be used in React, React‑Native, or any UI framework that consumes JSON.

Getting Started

Clone the repo, add your own LLM backend or use the provided OpenAI adapter, and run pnpm dev. Your new React project will automatically import the rendering logic. Feel free to extend the catalog or replace the registry with stylized components from your design system.

Tip – Use the built‑in docs & playground at localhost:3000 to experiment with prompts, catalog changes, and live preview without touching code.

Conclusion

JSON‑Render gives developers a clean, type‑safe way to let LLMs generate UI components. By keeping the output in a predictable, guarded JSON format, you maintain control over security, performance, and user experience—all while offering the powerful convenience of natural‑language UI design. Ready to bring AI‑generated dashboards into your product? Grab the package from NPM and start experimenting today.

Original Article: View Original

Share this article