What Is json-render? Vercel's Generative UI Framework (2026)
The idea in one sentence
json-render turns “let the AI build the interface” into “let the AI choose from the interface.” You publish a catalog of components and actions as Zod schemas; the model emits JSON that must fit that catalog; a renderer draws it. The model never writes markup, never calls a function, never invents a component. Vercel Labs describes it as “Generate dynamic, personalized UIs from prompts without sacrificing reliability.”
Verified September 21, 2026 against the vercel-labs/json-render README and package list on GitHub and npm.
Why it is trending in September 2026
Generative UI has been promised since Google’s Mini Apps and AI Mode’s information agents, but most implementations either render the model’s raw HTML (unsafe, unstyled, unpredictable) or hand-build every possible view (not generative). json-render is the guardrailed middle: the model decides which Card, Metric, Chart or Form appears and with what props; you decide what those components are. The framework climbed to roughly 17.5k stars and has been adding hundreds a day through September 2026, helped by a renderer list that now covers essentially every JavaScript surface and by an MCP Apps package that lets the same catalogs power UI inside Claude, ChatGPT, Cursor and VS Code.
How it works
1. Define the catalog (what the model may use).
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" },
},
});
The description strings are not decoration — the core package uses them to build the prompt that tells the model what each component and action is for.
2. Define the registry (how each component renders).
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>{props.value}</span></div>,
Button: ({ props, emit }) => <button onClick={() => emit("press")}>{props.label}</button>,
},
});
3. Let the model produce a spec. The wire format is flat: a root id plus an elements map, each element { type, props, children: [ids] }. Flat rather than nested means the renderer can paint card-1 before button-1 has streamed in.
4. Render. <Renderer spec={spec} registry={registry} />. Buttons emit events; actions are names the model can reference but only your handler can execute.
The same catalog drives @json-render/vue, @json-render/svelte (Svelte 5 runes), @json-render/solid and @json-render/react-native; @json-render/next and @json-render/tanstack-start extend the idea to full apps with routes, layouts, SSR and head metadata.
What ships in the box
| Package group | What you get |
|---|---|
| Core | Schemas, catalogs, AI prompt generation, dynamic props, SpecStream utilities |
| Web renderers | React, Vue 3, Svelte 5, SolidJS |
| App renderers | Next.js, TanStack Start |
| Component kits | @json-render/shadcn (36 shadcn/ui components), @json-render/shadcn-svelte |
| Non-DOM renderers | React Native, Remotion (video), react-pdf, react-email, Ink (terminal), Satori image (OG/social), React Three Fiber (20 components incl. GaussianSplat) |
| State | Adapters for Redux, Zustand, Jotai, XState Store |
| Directives | $format, $math, $concat, $count, $truncate, $pluralize, $join, $t (i18n) |
| Tooling | Devtools (spec tree, state editor, action log, stream log, catalog browser, DOM picker) for React/Vue/Svelte/Solid; codegen from spec trees; YAML wire format with streaming parser |
| Agents | @json-render/mcp — MCP Apps integration for Claude, ChatGPT, Cursor, VS Code |
Where it fits — and where it doesn’t
Good fits: analytics dashboards that reshape themselves per question; agent front-ends where an agent harness returns structured results and needs a UI without a designer in the loop; internal tools; onboarding and forms that adapt to the user; MCP Apps that must render safely inside someone else’s chat client; reports that need HTML, PDF and email variants from one spec.
Poor fits: open-ended page design (a catalog is a ceiling by construction); anything where the model must produce novel visuals; teams that want the model to write real code they will maintain (use a coding agent — see build vs buy vs fork with AI coding agents).
The one rule: the safety property is only as strong as the catalog. A RawHtml component, an unconstrained style prop, or an action that accepts arbitrary URLs reintroduces the injection and unpredictability problems json-render exists to remove. Keep props typed, keep actions enumerated, and treat the spec the way you treat any structured LLM output: validated on the server before it reaches a client.
Maturity
Vercel Labs, Apache-2.0, 0.x releases moving quickly through 2026. Expect breaking changes between minors, read the changelog before upgrading, and pin versions. The devtools and the breadth of renderers are ahead of anything comparable; the documentation is README-first. It is the most complete answer today to “how do I let a model build UI without letting it build anything.”