# json-render

> Let a model write JSON, not components, and render it with your own UI

- Category: App UIs & Builders / Generative UI SDKs
- Language: TypeScript
- Licence: Apache-2.0
- Repository: https://github.com/vercel-labs/json-render
- Website: https://json-render.dev
- Canonical page: https://ai-tldr.dev/tools/json-render/
- Updated: 2026-09-18

## In short

json-render is an open-source framework from Vercel Labs for generative UI: instead of asking a model to write component code, you ask it for JSON that can only describe components you already shipped. It sits in the Generative UI SDKs category of the App UIs & Builders tools and is open-source, written in TypeScript and licensed Apache-2.0.

## Overview

json-render is an open-source framework from Vercel Labs for generative UI: instead of asking a model to write component code, you ask it for JSON that can only describe components you already shipped. The workflow the project describes is AI, then json-render, then UI. A developer defines a catalog of allowed components and actions; the model produces a JSON spec constrained to that catalog; json-render maps the spec onto the real implementations. A generation that does not fit the catalog fails validation rather than rendering something unexpected.

The framework is split into a core package and one package per rendering target. @json-render/core holds the schemas, catalogs, AI prompts and expression evaluation, and the renderers cover web (React, Vue 3, Svelte 5, SolidJS), mobile (React Native), documents (PDF via react-pdf, email via react-email), video (Remotion), 3D (React Three Fiber), terminal UIs (Ink), and whole applications (Next.js and, since 0.21.0, TanStack Start). A companion @json-render/shadcn package ships 36 pre-built shadcn/ui components so a catalog can be useful on day one.

Specs are more than static markup. Props can reference application state through expressions such as { "$state": "/user/name" } or { "$computed": "functionName" }, elements can show or hide on state conditions, setState actions update the state model and re-evaluate anything that depends on it, and state watchers can fire custom actions on change. SpecStream utilities render progressively as the model streams its answer, so the interface fills in rather than appearing all at once.

## What it does

- Catalog-constrained generation: defineCatalog declares the components and actions a model may use, with Zod prop schemas, so invalid output fails validation instead of rendering
- One spec, many targets: renderers for React, Vue 3, Svelte 5, SolidJS, React Native, React Three Fiber, Remotion video, PDF, email, HTML, Ink terminal UIs, Next.js and TanStack Start
- Data binding and state: props can reference state with $state and $computed expressions, conditional visibility, setState actions, and state watchers that trigger custom behaviour
- Progressive rendering: SpecStream renders a spec as the model streams it, instead of waiting for the full response
- Directives: ready-made utilities for date formatting, math, string manipulation and internationalisation, plus a custom directives API for your own JSON shapes
- 36 pre-built shadcn/ui components via @json-render/shadcn, and devtools panels for React, Vue, Svelte and Solid that map rendered DOM back to spec keys

## Getting started

Install the core package plus the renderer you want, declare a catalog, implement the components behind it, then render a model-produced spec.

1. Install

   Core plus a renderer. The shadcn package is optional and gives you 36 ready-made components.

```bash
# React with shadcn/ui components
npm install @json-render/core @json-render/react @json-render/shadcn

# Full Next.js apps
npm install @json-render/core @json-render/react @json-render/next

# Mobile
npm install @json-render/core @json-render/react-native
```

2. Define the catalog

   The catalog is the contract with the model: it lists every component the model is allowed to use, with a Zod schema for the props and a description the model reads.

```typescript
import { defineCatalog } from "@json-render/core";
import { z } from "zod";

const catalog = defineCatalog(schema, {
  components: {
    Card: {
      props: z.object({ title: z.string() }),
      description: "A card container",
    },
    Button: {
      props: z.object({ label: z.string() }),
      description: "Clickable button",
    },
  },
});
```

3. Implement the components

   defineRegistry binds each catalog entry to a real implementation. Components receive props and children, and call emit to raise the actions the catalog declared.

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

4. Render the model's spec

   Pass the JSON the model produced and the registry to the renderer. Nothing outside the catalog can reach the screen.

```tsx
<Renderer spec={spec} registry={registry} />
```

## When to use it

- Build a chat or agent surface where answers come back as real charts, forms, tables and cards instead of markdown
- Ship personalised dashboards and onboarding flows that a model assembles per user from components your design system already owns
- Generate the same spec into different targets, for example a web page, a PDF report and an email, without re-authoring the layout
- Stand up whole routed applications from a spec using the Next.js or TanStack Start renderers, with file-based routes and server rendering

## Version history

- 2026-09-18 v0.21.0: Added @json-render/tanstack-start, a renderer for JSON-defined applications with file-based routes, reusable layouts, SSR loaders, head metadata, prerender paths, client navigation and route fallbacks. Also added experimental spec composition (experimental_composeSpec and experimental_createEvaluator), catalog-declared named slots for Vue registries, and stabilized streamed React renders.
