Wink Pings

Vercel lets AI draw tables in Slack, rendering as it generates

Agent responses in Slack, Teams, and Discord are no longer just text. Vercel Labs' json-render/chat lets models directly output native cards, and a single component catalog can render across a dozen platforms. It's genuinely good-looking—and that's also where the concerns lie.

Vercel Labs has released a new package, @json-render/chat. The goal is straightforward: make AI Agents reply in Slack, Teams, and Discord not with a block of text, but with native cards containing tables, fields, charts, and buttons. As the model writes, the interface renders.

Original post: https://x.com/ctatedev/status/2118853661040295997

The previous workflow was for the model to output markdown or raw JSON, and the frontend would parse it itself—errors were the norm. json-render breaks the process into three fixed steps.

1. Define a catalog

Use zod to clearly define which components exist, what props each component accepts, and which actions can be triggered.

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" },
  },
});

2. Make the model speak according to the catalog

catalog.prompt() directly generates the system prompt, which includes component descriptions, prop schemas, and a list of actions. The model can only compose within this whitelist, and the generated JSON structure is constrained by the schema.

3. Render the spec

The output is a JSON spec: a root key plus an elements map.

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: [] },
  },
};

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

The safety boundary falls at this step: AI cannot create components outside the catalog, and the output is less likely to go off track.

Cross-platform support is the more practical part. The same catalog can render in React, Vue, Svelte, Solid, React Native, Next.js, TanStack Start, Remotion video, React PDF, React Email, Ink terminal, and react-three-fiber 3D scenes. For chat scenarios, use @json-render/chat, which comes with standard component definitions and streamCard:

chat.onNewMention(async (thread, message) => {
  const result = streamText({
    model: "anthropic/claude-haiku-4.5",
    system: catalog.prompt(),
    prompt: message.text,
  });
  await streamCard(thread, result.textStream);
});

Streaming output relies on SpecStream. createSpecStreamCompiler receives the chunks emitted by the model, returns the current result and new patches, and the UI updates accordingly.

It also includes 36 prebuilt shadcn/ui components, MCP integration (Claude, ChatGPT, Cursor, VS Code), and a devtools panel that opens with Ctrl/Cmd + Shift + J and is tree-shaken in production. The repository is here: https://github.com/vercel-labs/json-render

Beyond good looks

Kyle Ledbetter from the Vercel team said in a reply, 'that's cool as hell.' Another reply is more worth reading. Lane Henslee wrote: 'Hallucinated revenue numbers, paired with native tables and charts, will look much more official.'

That hits the nail on the head. In plain text, an obviously absurd number makes the reader pause. Put it in a neatly formatted card with complete fields, and that hesitation disappears. Generative UI makes output more credible, while also making it cheaper for errors to masquerade as truth.

Someone asked in a reply why not use the existing OpenUI instead of creating another standard. The README doesn't answer this question.

Someone else asked whether users can click buttons while the card is still streaming, and whether actions wait for rendering to finish or trigger immediately. These kinds of interaction boundaries aren't covered much in the docs.

Vercel Labs' usual approach is to quickly release small experimental packages—good-looking demos that work—and then see if the community picks them up. This package is probably the same. Whether to use it depends on whether you can accept one premise: handing UI generation power to the model while trusting that your catalog boundaries are strict enough. Once the boundaries loosen, the interfaces the model creates are even more deceptive than the words it says.

发布时间: 2026-11-08 03:35