Issues, threads and tickets go into an orchestrator, which hands each one to the next free agent with `fanout="one"`. Every card is a plain `div` with `data-circuit`; they light up on `data-circuit-pulse` and the counters tick through `onArrive`. Hover a card to see the path through it. Narrow the frame and the columns become rows: the traces turn to run top to bottom by themselves.
registry/demos/circuit-demo.tsx
"use client";
import * as React from "react";
import { Circuit } from "@/components/ui/circuit";
const SOURCES = [
{ id: "github", name: "GitHub", meta: "issue #482", icon: "M8 3.5a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9Zm0 3a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Z" },
{ id: "slack", name: "Slack", meta: "#support", icon: "M3 4.5A1.5 1.5 0 0 1 4.5 3h7A1.5 1.5 0 0 1 13 4.5v5a1.5 1.5 0 0 1-1.5 1.5H7l-3 2.5V11h.5A1.5 1.5 0 0 1 3 9.5Z" },
{ id: "linear", name: "Linear", meta: "ENG-219", icon: "M3 5.5 5.5 3h7.5v7.5L10.5 13H3Zm3 1.5h4M6 9h2.5" },
];
const AGENTS = [
{ id: "coder", name: "Coder", verb: "patched" },
{ id: "reviewer", name: "Reviewer", verb: "reviewed" },
{ id: "tester", name: "Tester", verb: "verified" },
];
// What every card shares: dims when another card is hovered, lights when a signal lands.
const card =
"rounded-xl border border-zinc-200 bg-white shadow-xs transition-[opacity,border-color,box-shadow] duration-300 dark:border-zinc-800 dark:bg-zinc-900 data-[circuit-state=dimmed]:opacity-45 data-[circuit-state=active]:border-zinc-400 dark:data-[circuit-state=active]:border-zinc-600 data-circuit-pulse:border-sky-400 data-circuit-pulse:shadow-[0_0_0_4px_rgb(14_165_233/0.14)] dark:data-circuit-pulse:border-sky-500";
function Icon({ d }: { d: string }) {
return (
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth={1.4} strokeLinejoin="round" className="size-4" aria-hidden="true">
<path d={d} />
</svg>
);
}
export function CircuitDemo() {
const [done, setDone] = React.useState<Record<string, number>>({ coder: 14, reviewer: 9, tester: 11 });
const [routed, setRouted] = React.useState(128);
return (
<div className="@container w-full max-w-4xl">
<Circuit
flow="github, slack, linear -> orchestrator -> coder, reviewer, tester"
fanout="one"
onArrive={(node) => {
if (node === "orchestrator") setRouted((n) => n + 1);
else setDone((d) => ({ ...d, [node]: (d[node] ?? 0) + 1 }));
}}
className="grid items-center gap-y-12 px-1 py-2 @3xl:grid-cols-[1fr_minmax(0,15rem)_1fr] @3xl:gap-x-16"
>
<div className="grid grid-cols-3 gap-2.5 @3xl:grid-cols-1 @3xl:gap-3">
{SOURCES.map((s) => (
<div key={s.id} data-circuit={s.id} tabIndex={0} className={`${card} flex flex-col items-center gap-1.5 px-2 py-3 outline-none focus-visible:ring-2 focus-visible:ring-sky-400/60 @3xl:flex-row @3xl:gap-3 @3xl:px-3.5 @3xl:py-3`}>
<span className="grid size-7 shrink-0 place-items-center rounded-lg bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300">
<Icon d={s.icon} />
</span>
<span className="flex min-w-0 flex-col items-center @3xl:items-start">
<span className="text-sm font-medium text-zinc-900 dark:text-zinc-100">{s.name}</span>
<span className="font-mono text-[11px] text-zinc-500">{s.meta}</span>
</span>
</div>
))}
</div>
<div data-circuit="orchestrator" tabIndex={0} className={`${card} mx-auto w-full max-w-xs p-4 outline-none focus-visible:ring-2 focus-visible:ring-sky-400/60`}>
<div className="flex items-center justify-between">
<span className="rounded-md bg-sky-500/10 px-1.5 py-0.5 font-mono text-[10px] font-medium tracking-wide text-sky-700 dark:text-sky-300">
ORCHESTRATOR
</span>
<span className="relative flex size-2">
<span className="absolute inset-0 animate-ping rounded-full bg-emerald-400/60 motion-reduce:animate-none" />
<span className="relative size-2 rounded-full bg-emerald-500" />
</span>
</div>
<p className="mt-3 text-[15px] font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">Triage & assign</p>
<p className="mt-1 text-xs leading-relaxed text-zinc-500 dark:text-zinc-400">
Dedupes the event, pulls repo context and hands it to the next free agent.
</p>
<p className="mt-3 border-t border-zinc-100 pt-2.5 font-mono text-[11px] text-zinc-500 tabular-nums dark:border-zinc-800">
{routed} routed today
</p>
</div>
<div className="grid grid-cols-3 gap-2.5 @3xl:grid-cols-1 @3xl:gap-3">
{AGENTS.map((a) => (
<div key={a.id} data-circuit={a.id} tabIndex={0} className={`${card} flex flex-col items-center gap-0.5 px-2 py-3 outline-none focus-visible:ring-2 focus-visible:ring-sky-400/60 @3xl:flex-row @3xl:justify-between @3xl:px-3.5`}>
<span className="flex items-center gap-2 text-sm font-medium text-zinc-900 dark:text-zinc-100">
<span className="hidden size-1.5 rounded-full bg-sky-500 @3xl:block" />
{a.name}
</span>
<span className="font-mono text-[11px] text-zinc-500 tabular-nums">
{done[a.id]} {a.verb}
</span>
</div>
))}
</div>
</Circuit>
</div>
);
}
Live preview, examples and props: https://keyframeui.com/components/circuit#circuit-demo