An AI inbox in the middle of six lines: raw messages ride in and dive under the card, sorted tickets, replies and leads ride out the other side.
registry/demos/throughline-pipeline-demo.tsx
import * as React from "react";
import { Throughline, type ThroughlineItem, type ThroughlineLine } from "@/components/ui/throughline";
/** An ink glyph on plain glass: something raw, on its way in. */
function Raw({ children }: { children: React.ReactNode }) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" className="text-zinc-900 dark:text-white">
{children}
</svg>
);
}
/** A colored chip with a white glyph: something sorted, on its way out. */
function Sorted({ bg, children }: { bg: string; children: React.ReactNode }) {
return (
<span className="flex items-center justify-center rounded-[26%]" style={{ background: bg }}>
<svg viewBox="0 0 24 24" className="size-[62%]" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
{children}
</svg>
</span>
);
}
// Everything meets under the card at [50, 50]; the card hides both stations there.
const LINES: ThroughlineLine[] = [
{ from: [2, 14], to: [50, 50], bend: -0.12, behind: true },
{ from: [1, 54], to: [50, 50], bend: 0.04, behind: true },
{ from: [3, 92], to: [50, 50], bend: 0.12, behind: true },
{ from: [50, 50], to: [97, 12], bend: -0.12, behind: true },
{ from: [50, 50], to: [99, 48], bend: 0.04, behind: true },
{ from: [50, 50], to: [97, 90], bend: 0.12, behind: true },
];
const ITEMS: ThroughlineItem[] = [
{
label: "Email · “Refund for order 4471?”",
line: 0,
icon: (
<Raw>
<rect x="3.5" y="5.5" width="17" height="13" rx="2" />
<path d="m4 7 8 6 8-6" />
</Raw>
),
},
{
label: "Chat · “Can I add three seats?”",
line: 1,
icon: (
<Raw>
<path d="M5 18.5V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H8Z" />
<path d="M9 10h6M9 13h3.5" />
</Raw>
),
},
{
label: "Voicemail · 4 min, Spanish",
line: 2,
icon: (
<Raw>
<circle cx="7" cy="13" r="3" />
<circle cx="17" cy="13" r="3" />
<path d="M7 16h10" />
</Raw>
),
},
{
label: "Form · Enterprise demo request",
line: 0,
icon: (
<Raw>
<rect x="5" y="3.5" width="14" height="17" rx="2" />
<path d="M9 8.5h6M9 12h6M9 15.5h3" />
</Raw>
),
},
{
label: "Review · 2★, “checkout froze”",
line: 2,
icon: (
<Raw>
<path d="m12 4 2.3 4.8 5.2.7-3.8 3.6.9 5.2L12 15.8l-4.6 2.5.9-5.2-3.8-3.6 5.2-.7Z" />
</Raw>
),
},
{
label: "Slack thread · 14 replies",
line: 1,
icon: (
<Raw>
<path d="M8 4v16M16 4v16M4 8h16M4 16h16" />
</Raw>
),
},
{
label: "Ticket · Billing, P2",
line: 3,
icon: (
<Sorted bg="#2f3cff">
<path d="M4 9V6.5h16V9a3 3 0 0 0 0 6v2.5H4V15a3 3 0 0 0 0-6Z" />
</Sorted>
),
},
{
label: "Reply drafted · ready to send",
line: 4,
icon: (
<Sorted bg="#16a34a">
<path d="m5.5 12.5 4 4 9-9" />
</Sorted>
),
},
{
label: "Lead · sent to Ada in Sales",
line: 5,
icon: (
<Sorted bg="#ff5a1f">
<circle cx="12" cy="9" r="3.2" />
<path d="M5.5 19c1.2-3 3.7-4.5 6.5-4.5s5.3 1.5 6.5 4.5" />
</Sorted>
),
},
{
label: "Bug filed · CHK-212, with repro",
line: 4,
icon: (
<Sorted bg="#7b5cff">
<rect x="7.5" y="7" width="9" height="12" rx="4.5" />
<path d="M12 11v4M4.5 12h3M16.5 12h3M5.5 7.5l2.2 1.5M18.5 7.5l-2.2 1.5" />
</Sorted>
),
},
{
label: "Refund queued · $64.00",
line: 5,
icon: (
<Sorted bg="#2f3cff">
<path d="M15.5 8.5c-.6-1.2-1.9-2-3.5-2-2 0-3.5 1.1-3.5 2.7 0 3.6 7 1.9 7 5.6 0 1.6-1.5 2.7-3.5 2.7-1.6 0-2.9-.8-3.5-2M12 4.5v2M12 17.5v2" />
</Sorted>
),
},
{
label: "Alert · outage mentioned twice",
line: 3,
icon: (
<Sorted bg="#0b0b0f">
<path d="M12 8v5M12 16.5v.01" />
<path d="M10.3 4.6 3.6 17a2 2 0 0 0 1.7 3h13.4a2 2 0 0 0 1.7-3L13.7 4.6a2 2 0 0 0-3.4 0Z" />
</Sorted>
),
},
];
const QUEUES = [
{ name: "Billing", count: 412, share: 0.82, bar: "bg-[#2f3cff] dark:bg-[#7d86ff]" },
{ name: "Product bugs", count: 197, share: 0.46, bar: "bg-zinc-900 dark:bg-white/80" },
{ name: "Sales", count: 88, share: 0.24, bar: "bg-[#ff5a1f]" },
];
export function ThroughlinePipelineDemo() {
return (
<section className="mx-auto w-full max-w-5xl overflow-hidden rounded-[28px] bg-zinc-50 pt-12 text-zinc-950 dark:bg-[#0b0b10] dark:text-white">
<div className="px-6 text-center">
<p className="text-[13px] font-medium text-[#2f3cff] dark:text-[#7d86ff]">How Sift works</p>
<h2 className="mx-auto mt-3 max-w-[22rem] text-balance text-[clamp(1.75rem,4cqi,2.5rem)] font-semibold leading-[1.08] tracking-[-0.035em] sm:max-w-none">
Everything comes in. Only sorted work goes out.
</h2>
</div>
<Throughline lines={LINES} items={ITEMS} duration={22} className="mt-2 text-[#2f3cff] dark:text-[#7d86ff]">
<div className="flex justify-center px-6 py-[max(4.5rem,9cqi)]">
<div className="w-[min(19rem,70cqi)] rounded-[22px] border text-zinc-950 dark:text-white border-zinc-950/[0.07] bg-white p-5 shadow-[0_24px_48px_-24px_rgb(20_20_60/0.35)] dark:border-white/10 dark:bg-zinc-900 dark:shadow-[0_24px_48px_-24px_rgb(0_0_0/0.9)]">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="flex size-7 items-center justify-center rounded-[9px] bg-[#2f3cff]">
<svg viewBox="0 0 24 24" className="size-4" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round">
<path d="M5 7h14M8 12h8M11 17h2" />
</svg>
</span>
<span className="text-sm font-semibold tracking-[-0.01em]">Sift</span>
</div>
<span className="flex items-center gap-1.5 rounded-full bg-emerald-500/10 px-2 py-1 text-[11px] font-medium text-emerald-700 dark:text-emerald-400">
<span className="size-1.5 rounded-full bg-emerald-500" />
Live
</span>
</div>
<p className="mt-5 text-[2.5rem] font-semibold leading-none tracking-[-0.04em] tabular-nums">1,284</p>
<p className="mt-1.5 text-xs text-zinc-500 dark:text-zinc-400">messages triaged today, 3.1s each</p>
<ul className="mt-5 space-y-3">
{QUEUES.map((q) => (
<li key={q.name}>
<div className="flex items-baseline justify-between text-xs">
<span className="text-zinc-600 dark:text-zinc-300">{q.name}</span>
<span className="font-medium tabular-nums">{q.count}</span>
</div>
<div className="mt-1.5 h-1.5 rounded-full bg-zinc-100 dark:bg-white/10">
<div className={`h-full rounded-full ${q.bar}`} style={{ width: `${q.share * 100}%` }} />
</div>
</li>
))}
</ul>
</div>
</div>
</Throughline>
</section>
);
}
Live preview, examples and props: https://keyframeui.com/components/throughline#throughline-pipeline-demo