# Handoff Nav: AI Product

> An AI inbox's landing page. Scroll the page: the bar docks into a pill, and "Get Morrow" flies up into it as the hero passes under. Scroll back up and it flies home.

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

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

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/handoff-nav-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/handoff-nav.json`

## Notes from the registry

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

## Files

### `registry/demos/handoff-nav-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { HandoffNav, HandoffNavCta, HandoffNavProvider } from "@/components/ui/handoff-nav";

const links = [
  { label: "Product", href: "#product" },
  { label: "Pricing", href: "#pricing" },
  { label: "Changelog", href: "#changelog" },
  { label: "Docs", href: "#docs" },
];

const threads = [
  {
    from: "Priya Raman",
    subject: "Q3 board deck: one more pass?",
    preview: "Happy to tighten the retention slide before Thursday. I moved the cohort chart up and…",
    time: "6:12",
  },
  {
    from: "Lisbon offsite",
    subject: "Flights are booked, hotel needs a headcount",
    preview: "The venue holds rooms until Friday noon. Can you confirm 14 or should we keep 16?",
    time: "5:48",
  },
  {
    from: "Tomás at Harbor",
    subject: "Renewal terms for 2027",
    preview: "Attaching the redline. The only open item is the uptime credit in section 4.2.",
    time: "Yesterday",
  },
  {
    from: "Mei Chen",
    subject: "Coffee next week?",
    preview: "I'm in town Tuesday to Thursday. Would love to hear how the launch went.",
    time: "Yesterday",
  },
];

const steps = [
  {
    title: "It reads overnight",
    body: "Every new thread is read once, with the history behind it: what you promised, who's waiting, what changed.",
  },
  {
    title: "It drafts in your voice",
    body: "Replies sound like the last hundred you sent. Short where you're short, warm where you're warm.",
  },
  {
    title: "You send before coffee",
    body: "Open a thread, change a word if you want, press send. Most mornings take under ten minutes.",
  },
];

function Mark() {
  return (
    <svg viewBox="0 0 24 24" aria-hidden className="size-6">
      <circle cx="12" cy="12" r="10" className="fill-zinc-950 dark:fill-white" />
      <path d="M7 13.5a5 5 0 0 0 10 0" className="fill-none stroke-white stroke-2 dark:stroke-zinc-950" />
      <circle cx="12" cy="8.5" r="1.6" className="fill-violet-400" />
    </svg>
  );
}

export function HandoffNavDemo() {
  const scrollRef = React.useRef<HTMLDivElement>(null);

  return (
    <div className="relative w-full overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-950">
      <div ref={scrollRef} className="@container relative h-[var(--demo-height,36rem)] overflow-y-auto overscroll-contain">
        <HandoffNavProvider>
          <HandoffNav
            containerRef={scrollRef}
            className="[--handoff-nav-width:64rem]"
            logo={
              <a
                href="#"
                className="flex items-center gap-2 rounded-md text-[15px] font-semibold tracking-tight text-zinc-950 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-zinc-950 dark:text-white dark:focus-visible:outline-white"
              >
                <Mark />
                Morrow
              </a>
            }
            links={links}
            action={{ label: "Log in", href: "#login" }}
          />

          <main className="mx-auto max-w-5xl px-4 pb-16 @xl:px-6">
            <section className="flex flex-col items-center pt-10 text-center @xl:pt-14">
              <a
                href="#changelog"
                className="inline-flex items-center gap-2 rounded-full border border-zinc-950/10 bg-white py-1 pr-3 pl-1 text-[13px] text-zinc-700 shadow-sm dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-300"
              >
                <span className="rounded-full bg-violet-600 px-2 py-0.5 text-[11px] font-semibold text-white">New</span>
                Drafts now match your sign-off
                <span aria-hidden>→</span>
              </a>
              <h1 className="mt-6 max-w-2xl text-4xl/[1.05] font-semibold tracking-[-0.035em] text-balance text-zinc-950 @xl:text-6xl/[1.02] dark:text-white">
                Your inbox, answered{" "}
                <span className="text-zinc-400 dark:text-zinc-500">before you open it.</span>
              </h1>
              <p className="mt-5 max-w-md text-[15px]/6 text-pretty text-zinc-600 dark:text-zinc-400">
                Morrow reads every thread overnight and leaves a reply drafted in your voice. You read it,
                change a word, and send.
              </p>
              <div className="mt-8 flex items-center gap-5">
                <a
                  href="#product"
                  className="text-sm font-medium text-zinc-700 underline-offset-4 hover:underline dark:text-zinc-300"
                >
                  See a sample morning
                </a>
                <HandoffNavCta href="#signup">Get Morrow</HandoffNavCta>
              </div>
            </section>

            <section id="product" className="mt-14">
              <div className="overflow-hidden rounded-2xl border border-zinc-950/10 bg-white shadow-[0_24px_60px_-30px_rgb(9_9_11/0.35)] dark:border-white/10 dark:bg-zinc-900">
                <div className="flex items-center justify-between border-b border-zinc-950/[0.06] px-4 py-3 dark:border-white/[0.06]">
                  <p className="text-[13px] font-medium text-zinc-900 dark:text-zinc-100">Inbox</p>
                  <p className="text-[12px] text-zinc-500">4 drafts ready · 7:02 am</p>
                </div>
                <ul className="divide-y divide-zinc-950/[0.06] dark:divide-white/[0.06]">
                  {threads.map((thread) => (
                    <li key={thread.subject} className="flex items-start gap-3 px-4 py-3.5">
                      <span className="mt-0.5 grid size-8 shrink-0 place-items-center rounded-full bg-zinc-100 text-[12px] font-semibold text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300">
                        {thread.from
                          .split(" ")
                          .map((word) => word[0])
                          .slice(0, 2)
                          .join("")}
                      </span>
                      <div className="min-w-0 flex-1 text-left">
                        <div className="flex items-baseline justify-between gap-3">
                          <p className="truncate text-[13px] font-medium text-zinc-900 dark:text-zinc-100">
                            {thread.from}
                          </p>
                          <p className="shrink-0 text-[11px] text-zinc-400 tabular-nums">{thread.time}</p>
                        </div>
                        <p className="truncate text-[13px] text-zinc-700 dark:text-zinc-300">{thread.subject}</p>
                        <p className="truncate text-[12px] text-zinc-500">{thread.preview}</p>
                      </div>
                      <span className="mt-1 hidden shrink-0 rounded-full bg-violet-50 px-2 py-0.5 text-[11px] font-medium text-violet-700 @xl:inline dark:bg-violet-500/15 dark:text-violet-300">
                        Draft ready
                      </span>
                    </li>
                  ))}
                </ul>
              </div>
            </section>

            <section className="mt-20">
              <h2 className="max-w-md text-2xl/tight font-semibold tracking-tight text-zinc-950 dark:text-white">
                How a morning goes
              </h2>
              <ol className="mt-6 grid gap-px overflow-hidden rounded-2xl border border-zinc-950/10 bg-zinc-950/10 @2xl:grid-cols-3 dark:border-white/10 dark:bg-white/10">
                {steps.map((step, index) => (
                  <li key={step.title} className="bg-white p-5 dark:bg-zinc-900">
                    <p className="text-[12px] text-zinc-400 tabular-nums">{index + 1} of 3</p>
                    <p className="mt-6 text-[15px] font-medium text-zinc-900 dark:text-zinc-100">{step.title}</p>
                    <p className="mt-1.5 text-[13px]/5 text-zinc-500 dark:text-zinc-400">{step.body}</p>
                  </li>
                ))}
              </ol>
            </section>

            <section className="mt-20 grid gap-8 @2xl:grid-cols-[1fr_1.2fr] @2xl:items-end">
              <h2 className="text-2xl/tight font-semibold tracking-tight text-zinc-950 dark:text-white">
                Nothing sends without you
              </h2>
              <p className="text-[15px]/6 text-zinc-600 dark:text-zinc-400">
                Drafts wait in the thread until you press send. Morrow never replies on its own, never
                forwards, and never reads folders you haven&apos;t shared with it.
              </p>
            </section>

            <footer className="mt-24 flex items-center justify-between border-t border-zinc-950/10 pt-6 text-[13px] text-zinc-500 dark:border-white/10">
              <span>© 2026 Morrow Labs</span>
              <span>Privacy · Terms</span>
            </footer>
          </main>
        </HandoffNavProvider>
      </div>
    </div>
  );
}
```
