Custom node cards
Replace the default card per kind, or globally. The render callback receives the same context the default uses.
The default: WireNodeCardView
WireNodeCardView is exported from @aigentive/wire-react and drives every card unless you override renderNodeCard. It shows a kind chip, the node title, a subtitle (model, ref, or description depending on kind), and a one-line summary of the first two option values. It honors selection with a blue ring and follows the page theme.
You can call it directly inside your own renderer when you only want to extend the default — for example, wrap it with a header band:
import { WireNodeCardView, type WireNodeRenderContext } from "@aigentive/wire-react";
function BannerCard(ctx: WireNodeRenderContext) {
return (
<div className="grid gap-1">
<span className="rounded-t-lg bg-amber-100 px-2 py-1 text-[10px] font-bold uppercase">
beta
</span>
<WireNodeCardView {...ctx} />
</div>
);
}Built-in tones
Each card carries a tone. The inspector’s Card style dropdown sets it (Neutral is the default). Tones drive fill, border, and text colour together; for arbitrary colours, switch to Custom and edit fill / border / text directly.
When to customize
- You want a different visual surface (denser, monospace, branded color).
- You need to render runtime status — an outage badge, a queue depth.
- The kind needs a unique layout: a tool card with an input/output preview, an AI card with token cost.
You don't need a custom renderer to add badges, meta rows, progress, or footers — set those onnode.data.card and the default card renders them.
Three custom renderers
The same context, three different surfaces. Each is roughly 10–20 lines. None reach into canvas internals.
Minimal
function MinimalCard(ctx: WireNodeRenderContext) {
return (
<div className="grid h-[140px] content-center gap-1 rounded-lg border border-dashed
border-slate-300 bg-white px-4 py-3 text-center
dark:border-slate-700 dark:bg-slate-900">
<span className="text-[10px] font-bold uppercase tracking-wider
text-slate-500">{ctx.kind}</span>
<strong className="text-[14px] text-slate-950 dark:text-slate-50">{ctx.node.title}</strong>
<span className="font-mono text-[11px] text-slate-400">minimal</span>
</div>
);
}Terminal
function TerminalCard(ctx: WireNodeRenderContext) {
return (
<div
aria-selected={ctx.selected}
className={`grid h-[140px] content-start gap-1 rounded-lg border bg-slate-950
px-3.5 py-3 font-mono text-[12px] ${
ctx.selected ? "border-emerald-400 ring-2 ring-emerald-400/20" : "border-slate-700"
}`}
>
<span className="text-emerald-400">$ wire-{ctx.kind}</span>
<strong className="text-emerald-100">{ctx.node.title}</strong>
<span className="text-slate-500">id: {ctx.node.id}</span>
</div>
);
}Row
function RowCard(ctx: WireNodeRenderContext) {
return (
<div className="grid h-[140px] content-center gap-2 rounded-lg border border-slate-200
bg-white px-4 py-3 dark:border-slate-800 dark:bg-slate-900">
<div className="flex items-center gap-2">
<span className={`h-2 w-2 rounded-full ${kindDotClass(ctx.kind)}`} />
<span className="font-mono text-[11px] uppercase tracking-wider
text-slate-500">{ctx.kind}</span>
</div>
<strong className="text-[15px] text-slate-950">{ctx.node.title}</strong>
</div>
);
}Wire it up
Pass your renderer to WireWorkspace or WireCanvas:
<WireWorkspace
diagram={diagram}
onChange={setDiagram}
optionCatalog={catalog}
renderNodeCard={(ctx) =>
ctx.kind === "ai" ? <TerminalCard {...ctx} /> : <RowCard {...ctx} />
}
/>;Custom group frames
Groups (kind: "group") are nodes too — they get their own renderer slot via renderGroup. The same WireNodeRenderContext arrives, plus the canvas sizes the frame to fit its children. Use it to brand stage boundaries, show stage status, or add a header strip with quick actions.
function GroupFrame(ctx: WireNodeRenderContext) {
return (
<div
aria-selected={ctx.selected}
className={`grid h-full content-start gap-1 rounded-lg border-2 border-dashed
bg-slate-50/60 p-3 dark:bg-slate-900/40 ${
ctx.selected
? "border-blue-400 ring-2 ring-blue-400/20"
: "border-slate-300 dark:border-slate-700"
}`}
style={{ width: ctx.width, height: ctx.height }}
>
<span className="text-[10px] font-bold uppercase tracking-wider
text-slate-500 dark:text-slate-400">
Stage · {ctx.node.title}
</span>
</div>
);
}
<WireWorkspace
diagram={diagram}
onChange={setDiagram}
optionCatalog={catalog}
renderNodeCard={Card}
renderGroup={GroupFrame}
/>;