showcase/shell-docs/src/content/docs/generative-ui/hashbrown.mdx
You have a chat surface and you want the agent to draw a dashboard, not just describe one in prose. By the end of this guide, the agent will stream a structured output object, @hashbrownai/react's progressive JSON parser will hand each finished slice to your renderer as it arrives, and the user sees the dashboard fill in live.
If you'd rather work with an explicit catalog of registered components rather than streaming a JSON tree, see the sibling page BYOC — JSON Render for the same scenario implemented with @json-render/react.
The integration point is <CopilotChat>'s messageView.assistantMessage slot. Replace the default assistant-message renderer with a Hashbrown-backed one, and the chat takes care of everything else:
import {
CopilotKit,
CopilotChat,
useConfigureSuggestions,
} from "@copilotkit/react-core/v2";
import { HashBrownAssistantMessage } from "./hashbrown-renderer";
export default function ByocHashbrownDemo() {
useConfigureSuggestions({
suggestions: [
{ title: "Sales overview", message: "Show me a sales dashboard." },
{ title: "Region split", message: "Break down sales by region." },
],
available: "always",
});
return (
<CopilotKit runtimeUrl="/api/copilotkit-byoc-hashbrown" agent="byoc_hashbrown">
<CopilotChat
messageView={{ assistantMessage: HashBrownAssistantMessage }}
/>
</CopilotKit>
);
}
The custom renderer is where Hashbrown earns its keep. useJsonParser consumes the streaming text content of an assistant message and emits typed JSON values as they parse; useUiKit resolves component names against your catalog and renders them with their props:
import { useJsonParser, useUiKit } from "@hashbrownai/react";
import { MetricCard } from "./metric-card";
import { PieChart, BarChart } from "./charts";
const catalog = {
MetricCard,
PieChart,
BarChart,
};
export function HashBrownAssistantMessage({ message }: { message: AssistantMessage }) {
const parsed = useJsonParser(message.content ?? "");
const ui = useUiKit({ catalog, value: parsed });
return <div className="space-y-3">{ui}</div>;
}
Each component in the catalog is just a regular React component. The catalog acts as a typed allowlist: anything the agent emits that isn't in the catalog won't render, so the agent can't draw arbitrary HTML into the page.
The agent's job is to stream structured output, not text. How you do that depends on your framework, but the shape is always the same: emit a JSON object at the top level of the assistant message, where each child references a component name and props matching the catalog.
{
"type": "MetricCard",
"title": "Total revenue",
"value": 184302,
"delta": 0.07
}
Or a tree:
{
"type": "Stack",
"children": [
{ "type": "MetricCard", "title": "Total revenue", "value": 184302 },
{ "type": "BarChart", "data": [...] }
]
}
Hashbrown's parser tolerates partial JSON, so the user sees MetricCard resolve before BarChart even starts streaming.
Both byoc-hashbrown and byoc-json-render solve the same problem (agent-authored structured UI, rendered through a typed catalog), with two different rendering libraries. Pick whichever you already use elsewhere — the agent contract is the same shape; the React glue is what changes.