json-render: Generative UI from a component catalog, and dev needs portless
The Generative UI framework. json-render The Generative UI framework.** Generate dynamic, personalized UIs from prompts without sacrificing reliability.
At a glance
- What is it?
- json-render is a Vercel Labs framework in which a model emits JSON constrained to a catalog of components you define, and the runtime a consumer touches is three calls. The interesting details are in the scripts: the development command is gated on a second Vercel Labs tool installed globally, and twenty-nine packages are versioned in lockstep by a script pair.
- Who is it for?
- json-render fits a team with a model that can emit JSON and a set of components it is willing to render, and that wants the same spec to produce a web page, a PDF, an email or a video. It does not fit a team that needs the model to invent markup, because the framework's entire safety argument is a closed catalog.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- vercel-labs/json-render19,633 ★ · +3,856
| Repository | 2026-07-28 | 2026-11-14 |
|---|---|---|
| vercel-labs/json-render | 15.8k | 19.6k |
3,856 Stars in the last 109 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The guardrail is a Zod schema per component and a description the model reads
The first quick start step defines a catalog, and the shape of it is the whole design. Each entry is a component name, a `props` schema and a `description`:
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",
},Two things sit in each entry and they do different jobs. The Zod schema is the contract the output is validated against, which is where the claim that the JSON matches your schema every time comes from. The `description` is prose, so it is prompt surface rather than documentation.
That second point is the practical consequence. A component with a vague description gets used wrongly by the model, and the fix is editing a string in your catalog rather than adding validation. The `format` field shows the shape of that decision: a three-value enum that is explicitly nullable, so the model can say no format rather than guessing one.
Actions are emitted events, not functions the model can call
The second step maps catalog names to components, and the third argument each component receives is the interesting part:
Button: ({ props, emit }) => (
<button onClick={() => emit("press")}>{props.label}</button>
),So a component receives `props`, `children` and `emit`. Two of those are unremarkable. The third is the safety story: a generated spec cannot call a function, because there is nothing in the spec to call. It can only cause a component to emit a named event, and what that event means is decided by code the model never sees.
Put beside the catalog step, the two halves of the guardrail are complete. The schema decides what values arrive, and the event mechanism decides what can happen. A model that misunderstands the interface can produce a card with the wrong title, and it cannot produce a button that posts to an endpoint you did not wire.
The registry is also where the type safety comes from. `defineRegistry` is given the catalog, so the keys in the components object are checked against the names the catalog declared, which is why the third example can be a single line.
The wire format is a flat elements map, not a nested tree
The Renderers section shows the spec shape, and it is not the tree you would draw:
const spec = {
root: "card-1",
elements: {
"card-1": {
type: "Card",
props: { title: "Hello" },
children: ["button-1"],
},
"button-1": {
type: "Button",
props: { label: "Click me" },
children: [],
},
},
};There is a root key and an elements map. Each element has a type drawn from the catalog, its props, and children given as an array of keys rather than as nested objects.
A normalised document is a deliberate choice with two payoffs. Editing one element is a change to one entry rather than a rewrite of the containing subtree, which is what makes partial model output usable. And it is why the repository also ships a codegen package for generating code from a spec tree and a YAML package with a streaming parser and edit modes, because both operations are easier on a flat map than on a nested one.
The same shape also explains the directives package, which names eight operations with a dollar prefix including format, math, concat, count, truncate, pluralize, join and a translation function. Those are the vocabulary the model uses for values, which is a small language layered on a data format rather than an escape hatch.
The Svelte install line installs @json, not @json-render/svelte
The install block at the top of the README has one line per target. React installs `@json-render/core` and `@json-render/react`, React Native adds `@json-render/react-native`, video adds `@json-render/remotion`, PDF adds `@json-render/react-pdf`, email adds `@json-render/react-email` alongside two `@react-email` packages, and Vue adds `@json-render/vue`.
The Svelte line is the odd one:
# or for Svelte
npm install @json-render/core @jsonThat installs `@json`, not `@json-render/svelte`. And the packages table, further down the same file, is unambiguous that the Svelte renderer is `@json-render/svelte`, described as a Svelte 5 renderer with runes-based reactivity, with a matching `@json-render/shadcn-svelte` holding the same 36 pre-built components for Svelte.
The shadcn line above it, `npm install @json-render/shadcn`, is a single package with no core, which is correct given the table says it carries the components, and it is another shape a reader has to notice rather than assume.
pnpm dev is gated on a second Vercel Labs tool installed globally
The `predev` script is a guard for a development prerequisite:
"predev": "command -v portless >/dev/null 2>&1 || (echo '\\nportless is required but not installed. Run: npm i -g portless\\nSee: https://github.com/vercel-labs/portless\\n' && exit 1)",So the development command runs a shell test for a binary called `portless`, and if it is absent the script prints an instruction to install it globally with npm, links its repository, and exits non-zero.
`portless` is a separate Vercel Labs project, linked from the error message at github.com/vercel-labs/portless.
The rest of the scripts are conventional for a monorepo of this size. The dev command fans out through turbo with a concurrency of fifty, lint runs `turbo run lint`, the two type-check aliases both run `turbo run check-types`, the test commands are vitest, and the end-to-end suite is a filtered package. The formatting script is narrower than the rest, prettier restricted to TypeScript and TSX.
Twenty-seven packages, and a script pair keeps their versions in step
The packages table lists twenty-nine entries, and reading it as a group is more informative than reading it as a list.
The core holds schemas, catalogs, AI prompts, dynamic props and spec stream utilities. Then four web renderers, React, Vue, Svelte and Solid. Then two component libraries of 36 pre-built shadcn components each, one on Radix UI and Tailwind and one on Svelte 5. Then output targets that have little in common with each other: React Three Fiber for 3D scenes with 20 built-in components including GaussianSplat, Remotion for video, React PDF, React Email, Ink for terminal interfaces, and an image renderer that produces SVG and PNG through Satori. Then Next.js and TanStack Start, both of which take a spec to full applications with routes, layouts and server rendering. Then a devtools core with four framework adapters. Then four adapters for a single `StateStore` interface, backed by Redux and Redux Toolkit, Zustand, Jotai and XState. Then an MCP integration for Claude, ChatGPT, Cursor and VS Code, and a YAML wire format with a streaming parser, edit modes and an AI SDK transform.
The cost of that breadth shows up in the scripts. There is a `version:sync` script and a `version:check` script, and publishing filters on the `@json-render/*` scope, because a version written into twenty-nine manifests by hand is not a thing anyone maintains. The version pair is the mechanism that makes the breadth tolerable.
Publishing uses npm provenance, and one example has no AI in it
The publish script is worth reading for two flags:
"ci:publish": "pnpm run build && pnpm -r publish --provenance --no-git-checks --access public --filter '@json-render/*'",`--provenance` asks npm to attach a supply-chain attestation to each published package, linking it to the build that produced it. That is not a default, and it is the single most reassuring line in the manifest for anyone worried about a twenty-nine-package scope appearing on a registry.
`--no-git-checks` is the opposite kind of decision: it removes the guard that would normally refuse to publish from a dirty working tree or without a matching tag. Combined, they say the release is attested but not gated on repository state.
Now look at the examples directory. Among twenty-three examples, one is named `no-ai/`. That is the most interesting entry in the list, because it establishes that a spec is data rather than model output: you can hand-write it, render it, and develop the whole pipeline with no model in the loop and no API key.
The other unusual ones follow the package table. `gsplat/` and `react-three-fiber-gsplat/` cover the splatting renderer, `next-website-builder/` takes a spec to a whole site, `stripe-app/` and `game-engine/` show the components doing real work.
Editorial conclusion
json-render fits a team with a model that can emit JSON and a set of components it is willing to render, and that wants the same spec to produce a web page, a PDF, an email or a video. It does not fit a team that needs the model to invent markup, because the framework's entire safety argument is a closed catalog. Before adopting, check three things: the install command for your target, which for Svelte should use `@json-render/svelte` as the packages table names it, whether you can run the development server, because the `predev` guard requires `npm i -g portless` first, and the version policy, since at v0.21.4 the project is pre-1.0 and a minor bump under that line is conventionally breaking across all twenty-nine packages.
Frequently asked questions
What is json render?
It is a Generative UI framework from Vercel Labs in which a model generates interfaces from natural language prompts, constrained to components you define, so the JSON output matches your schema. The runtime surface is three calls: `defineCatalog` for the component catalog, `defineRegistry` to map those names to components, and a `Renderer` that takes the model's spec and that registry.
How do I use json render?
Install the core package plus the renderer for your target, and the README lists one install line per target including React, React Native, Remotion for video, React PDF, React Email, Vue and Svelte. Then define a catalog whose entries pair a Zod props schema with a description, define a registry whose components receive `props`, `children` and `emit`, and render the model's output with the `Renderer` component.
Is JSON-Render available in Vercel?
It is a Vercel Labs project published under the Apache-2.0 licence as the `@json-render/*` packages on npm, with `@json-render/core` holding the schemas, catalogs, AI prompts and spec stream utilities. The README links the Vercel Labs product listing, and the development command additionally requires a second Vercel Labs tool, portless, installed globally with `npm i -g portless`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/vercel-labs-json-render)Star history chart for your README
[](https://hysenlabs.com/projects/vercel-labs-json-render)Was this useful?