A payment webhook through a queue and a worker to three destinations, fired by a button through `apiRef.send()` with `autoPlay={false}`. Each destination's trace carries its own `color`, and the delivery log fills from `onArrive`.
@keyframeui
registry/demos/circuit-pipeline-demo.tsx
"use client";
import * as React from "react";
import { Circuit, type CircuitControls } from "@/components/ui/circuit";
const STEPS = [
{ id: "stripe", label: "POST /webhooks/stripe", meta: "invoice.paid" },
{ id: "queue", label: "Queue", meta: "events · fifo" },
{ id: "worker", label: "Worker", meta: "handlePaid()" },
];
const SINKS = [
{ id: "db", label: "Postgres", meta: "upsert", color: "#34d399" },
{ id: "email", label: "Email", meta: "receipt", color: "#a78bfa" },
{ id: "slack", label: "Slack", meta: "#revenue", color: "#fbbf24" },
];
const card =
"rounded-lg border border-zinc-200 bg-white px-3 py-2 transition-[opacity,border-color,box-shadow] duration-300 dark:border-zinc-800 dark:bg-zinc-900 data-[circuit-state=dimmed]:opacity-45 data-circuit-pulse:border-zinc-400 data-circuit-pulse:shadow-[0_0_0_4px_rgb(113_113_122/0.14)] dark:data-circuit-pulse:border-zinc-500";
type Entry = { id: number; at: string; text: string };
export function CircuitPipelineDemo() {
const api = React.useRef<CircuitControls>(null);
const [log, setLog] = React.useState<Entry[]>([]);
const next = React.useRef(0);
return (
<div className="@container w-full max-w-3xl">
<div className="grid gap-8 @2xl:grid-cols-[minmax(0,1fr)_15rem]">
<Circuit
apiRef={api}
autoPlay={false}
speed={320}
flow={[
"stripe -> queue -> worker",
...SINKS.map((s) => ({ from: "worker", to: s.id, color: s.color })),
]}
onArrive={(node, from) => {
const time = new Date().toLocaleTimeString([], { hour12: false });
next.current += 1;
const id = next.current;
setLog((l) => [{ id, at: time, text: `${from} → ${node}` }, ...l].slice(0, 7));
}}
packetClassName="text-zinc-900 dark:text-zinc-100"
className="flex flex-col items-center gap-9"
>
{STEPS.map((s) => (
<div key={s.id} data-circuit={s.id} className={`${card} w-56 text-center`}>
<p className="font-mono text-xs font-medium text-zinc-900 dark:text-zinc-100">{s.label}</p>
<p className="font-mono text-[11px] text-zinc-500">{s.meta}</p>
</div>
))}
<div className="grid w-full grid-cols-3 gap-3">
{SINKS.map((s) => (
<div key={s.id} data-circuit={s.id} className={`${card} text-center`}>
<p className="flex items-center justify-center gap-1.5 text-xs font-medium text-zinc-900 dark:text-zinc-100">
<span className="size-1.5 rounded-full" style={{ background: s.color }} />
{s.label}
</p>
<p className="font-mono text-[11px] text-zinc-500">{s.meta}</p>
</div>
))}
</div>
</Circuit>
<div className="flex flex-col gap-3">
<button
type="button"
onClick={() => api.current?.send("stripe")}
className="h-9 rounded-lg bg-zinc-900 px-3 text-sm font-medium text-white transition-transform active:scale-[0.98] dark:bg-zinc-100 dark:text-zinc-900"
>
Send test event
</button>
<ol className="flex min-h-48 flex-col gap-1 rounded-lg border border-dashed border-zinc-200 p-3 font-mono text-[11px] dark:border-zinc-800">
{log.length === 0 && <li className="text-zinc-400">No deliveries yet.</li>}
{log.map((e) => (
<li key={e.id} className="flex justify-between gap-3 text-zinc-600 dark:text-zinc-400">
<span>{e.text}</span>
<span className="text-zinc-400 dark:text-zinc-600">{e.at}</span>
</li>
))}
</ol>
</div>
</div>
</div>
);
}
Live preview, examples and props: https://keyframeui.com/components/circuit#circuit-pipeline-demo



















