# Throughline: Pipeline

> 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.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/throughline-pipeline-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/throughline-pipeline-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/throughline-pipeline-demo/light.png
- Registry item: https://keyframeui.com/r/throughline-pipeline-demo.json
- Categories: hero, interaction, motion
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/throughline-pipeline-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/throughline.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/throughline#throughline-pipeline-demo

## Files

### `registry/demos/throughline-pipeline-demo.tsx`

```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>
  );
}
```
