Handoff Nav

Sticky Navbar on Scroll for React

A navbar that tightens into a floating pill once the page scrolls, and catches your hero's call to action: as the hero button rides up behind the pill it flies into a slot at the end, and when you scroll back it flies home. On narrow screens the pill grows down into a menu.

  • Navigation
  • Scroll
  • Interaction

Uses motion, clsx, tailwind-merge

Open in ChatGPTOpen in Claude
dockAt24px
dockDuration0.40s
catchDuration0.45s
catchBounce0.08
<HandoffNav />

The demo follows as you drag.

NewDrafts now match your sign-off
Your inbox, answered before you open it.

Morrow reads every thread overnight and leaves a reply drafted in your voice. You read it, change a word, and send.

Inbox

4 drafts ready · 7:02 am

  • PR

    Priya Raman

    6:12

    Q3 board deck: one more pass?

    Happy to tighten the retention slide before Thursday. I moved the cohort chart up and…

  • Lo

    Lisbon offsite

    5:48

    Flights are booked, hotel needs a headcount

    The venue holds rooms until Friday noon. Can you confirm 14 or should we keep 16?

  • Ta

    Tomás at Harbor

    Yesterday

    Renewal terms for 2027

    Attaching the redline. The only open item is the uptime credit in section 4.2.

  • MC

    Mei Chen

    Yesterday

    Coffee next week?

    I'm in town Tuesday to Thursday. Would love to hear how the launch went.

How a morning goes
  1. 1 of 3

    It reads overnight

    Every new thread is read once, with the history behind it: what you promised, who's waiting, what changed.

  2. 2 of 3

    It drafts in your voice

    Replies sound like the last hundred you sent. Short where you're short, warm where you're warm.

  3. 3 of 3

    You send before coffee

    Open a thread, change a word if you want, press send. Most mornings take under ten minutes.

Nothing sends without you

Drafts wait in the thread until you press send. Morrow never replies on its own, never forwards, and never reads folders you haven't shared with it.

© 2026 Morrow LabsPrivacy · Terms
Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Handoff Nav: A navbar that tightens into a floating pill once the page scrolls, and catches your hero's call to action: as the hero button rides up behind the pill it flies into a slot at the end, and when you scroll back it flies home. On narrow screens the pill grows down into a menu.
This demo, 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.

1. Install it:
   npx shadcn@latest add @keyframeui/handoff-nav-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/handoff-nav-demo.json instead.)
   It adds the demo, components/handoff-nav-demo.tsx, and the component, components/ui/handoff-nav.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <HandoffNav> on its own.

3. These props set its feel. Change them before you change the file:
   - dockAt (default 24): Pixels of scroll before the bar docks into the pill.
   - dockDuration (default 0.4): Seconds the bar takes to tighten into the pill once the page scrolls. Spreading back out at the top stays a little quicker.
   - catchDuration (default 0.45): Seconds the hero's button takes to fly up into the pill. The flight home scales with it.
   - catchBounce (default 0.08): How much the caught button overshoots its slot before it settles, from `0` (none) up.

4. Don't change, in components/ui/handoff-nav.tsx:
   - The caught button is a second copy in the pill's slot, flown by `transform`, width and height on one spring's progress; the hero keeps an empty space its size, so nothing under it moves.
   - A flight runs in two legs so the button never crosses the links: up, it slides along under the pill and rises into the slot last; home, it drops out of the pill first and then slides.
   - The label only travels, it never scales: it sits in one span that keeps its own width, which is also what the pill measures to open the slot.
   - The hand between copies is invisible: the copy is placed over its start before the original hides, coming home both show for a frame or two (never neither), and focus moves with it.
   - Reduced motion: nothing travels. The bar swaps to the pill at once, the button crossfades between hero and pill, and the menu opens without growing.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/handoff-nav.md

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/handoff-nav.tsx and installs motion, clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/handoff-nav

Examples

Finished sections built with Handoff Nav. Each one installs whole, with the component included.

Sticky Add to Bag

A product page where "Add to bag" moves into the nav, so it's one click away all the way down to the reviews. Either copy adds to the bag, and the action's count and the button's label update in both places.

The Weekender in Tobacco

Bags / Weekenders

The Weekender

Waxed canvas · 42 L

$189

Colour: Tobacco

Ships tomorrow · free returns

Material
18 oz waxed cotton canvas, vegetable-tanned leather trim
Size
52 × 28 × 30 cm · 42 litres · fits most overhead bins
Inside
Zip pocket, two slip pockets, a padded sleeve for a 16-inch laptop
Care
Brush off dry dirt. Re-wax once a year with our bar, sold separately
What owners say

4.8 from 312 reviews

“Two winters of trains and it looks better than the day it came. The wax took a crease and kept it.”
Hannah L. · Copenhagen
“Holds four days of clothes and my laptop without looking stuffed. The shoulder strap pad is the detail I didn't know I needed.”
Owen T. · Portland
Pairs well with
Field & Fold, PortlandShipping · Returns
Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Handoff Nav: A navbar that tightens into a floating pill once the page scrolls, and catches your hero's call to action: as the hero button rides up behind the pill it flies into a slot at the end, and when you scroll back it flies home. On narrow screens the pill grows down into a menu.
This demo, Sticky Add to Bag: A product page where "Add to bag" moves into the nav, so it's one click away all the way down to the reviews. Either copy adds to the bag, and the action's count and the button's label update in both places.

1. Install it:
   npx shadcn@latest add @keyframeui/handoff-nav-store-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/handoff-nav-store-demo.json instead.)
   It adds the demo, components/handoff-nav-store-demo.tsx, and the component, components/ui/handoff-nav.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <HandoffNav> on its own.

3. These props set its feel. Change them before you change the file:
   - dockAt (default 24): Pixels of scroll before the bar docks into the pill.
   - dockDuration (default 0.4): Seconds the bar takes to tighten into the pill once the page scrolls. Spreading back out at the top stays a little quicker.
   - catchDuration (default 0.45): Seconds the hero's button takes to fly up into the pill. The flight home scales with it.
   - catchBounce (default 0.08): How much the caught button overshoots its slot before it settles, from `0` (none) up.

4. Don't change, in components/ui/handoff-nav.tsx:
   - The caught button is a second copy in the pill's slot, flown by `transform`, width and height on one spring's progress; the hero keeps an empty space its size, so nothing under it moves.
   - A flight runs in two legs so the button never crosses the links: up, it slides along under the pill and rises into the slot last; home, it drops out of the pill first and then slides.
   - The label only travels, it never scales: it sits in one span that keeps its own width, which is also what the pill measures to open the slot.
   - The hand between copies is invisible: the copy is placed over its start before the original hides, coming home both show for a frame or two (never neither), and focus moves with it.
   - Reduced motion: nothing travels. The bar swaps to the pill at once, the button crossfades between hero and pill, and the menu opens without growing.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/handoff-nav.md

Studio at Phone Width

An interiors studio's site in a phone frame. The links fold into a menu that grows out of the pill, and "Book a call" shares the pill with it once caught; the hero copy gets wider padding through `data-[placement=hero]:`.

Interiors · Lisbon

Rooms that hold still.

We design homes around the light they already have, and finish them with things made a few streets away.

Rua das Flores, Apartment, 92 m²
Rua das FloresApartment, 92 m² · 2026
Casa Alfama, Kitchen and dining
Casa AlfamaKitchen and dining · 2025
Quinta Norte, Guest suite
Quinta NorteGuest suite · 2025
Atelier Graça, Reading room
Atelier GraçaReading room · 2024
What we take on
  • Whole homes
  • Kitchens and joinery
  • Lighting plans
  • Furniture sourcing
Rua do Benformoso 41, Lisbon
Two new homes a season. Next opening: March.
Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Handoff Nav: A navbar that tightens into a floating pill once the page scrolls, and catches your hero's call to action: as the hero button rides up behind the pill it flies into a slot at the end, and when you scroll back it flies home. On narrow screens the pill grows down into a menu.
This demo, Studio at Phone Width: An interiors studio's site in a phone frame. The links fold into a menu that grows out of the pill, and "Book a call" shares the pill with it once caught; the hero copy gets wider padding through `data-[placement=hero]:`.

1. Install it:
   npx shadcn@latest add @keyframeui/handoff-nav-studio-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/handoff-nav-studio-demo.json instead.)
   It adds the demo, components/handoff-nav-studio-demo.tsx, and the component, components/ui/handoff-nav.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <HandoffNav> on its own.

3. These props set its feel. Change them before you change the file:
   - dockAt (default 24): Pixels of scroll before the bar docks into the pill.
   - dockDuration (default 0.4): Seconds the bar takes to tighten into the pill once the page scrolls. Spreading back out at the top stays a little quicker.
   - catchDuration (default 0.45): Seconds the hero's button takes to fly up into the pill. The flight home scales with it.
   - catchBounce (default 0.08): How much the caught button overshoots its slot before it settles, from `0` (none) up.

4. Don't change, in components/ui/handoff-nav.tsx:
   - The caught button is a second copy in the pill's slot, flown by `transform`, width and height on one spring's progress; the hero keeps an empty space its size, so nothing under it moves.
   - A flight runs in two legs so the button never crosses the links: up, it slides along under the pill and rises into the slot last; home, it drops out of the pill first and then slides.
   - The label only travels, it never scales: it sits in one span that keeps its own width, which is also what the pill measures to open the slot.
   - The hand between copies is invisible: the copy is placed over its start before the original hides, coming home both show for a frame or two (never neither), and focus moves with it.
   - Reduced motion: nothing travels. The bar swaps to the pill at once, the button crossfades between hero and pill, and the menu opens without growing.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/handoff-nav.md

Props

logo Required

TypeReact.ReactNode

Your logo or wordmark, at the start of the bar. Wrap it in a link to your home page.

links

TypeHandoffNavLink[]Default[]

The page links, each { label, href }. They sit at the end of the bar and fold into a menu on narrow screens.

action

TypeHandoffNavLink

One quieter action after the links, like "Log in", drawn as an outlined pill.

dockAt

TypenumberDefault24

Pixels of scroll before the bar docks into the pill.

dockDuration

TypenumberDefault0.4

Seconds the bar takes to tighten into the pill once the page scrolls. Spreading back out at the top stays a little quicker.

catchDuration

TypenumberDefault0.45

Seconds the hero's button takes to fly up into the pill. The flight home scales with it.

catchBounce

TypenumberDefault0.08

How much the caught button overshoots its slot before it settles, from 0 (none) up.

containerRef

TypeReact.RefObject<HTMLElement | null>

The element that scrolls, when it isn't the window. The nav then sticks to the top of that box: put it first inside it. Without it, the nav is fixed to the viewport and keeps its 64px in the page.

surfaceClassName

Typestring

Classes for the docked pill's surface: its fill, border and shadow.

className

Typestring

Extra classes for the <header>. Set [--handoff-nav-width:64rem] to match the bar's resting width to your page's container (72rem by default).

cta.children Required

TypeReact.ReactNode

HandoffNavCta: the button's label. The same content is drawn in the nav once it's caught, at the same size: only the button around it changes.

cta.href

Typestring

HandoffNavCta: where the button goes. Without it, it's a <button>.

cta.onClick

TypeReact.MouseEventHandler<HTMLElement>

HandoffNavCta: called when either copy is clicked, in the hero or in the nav.

cta.className

Typestring

HandoffNavCta: classes for both copies. Target one with data-[placement=hero]: or data-[placement=nav]:.

provider.children Required

TypeReact.ReactNode

HandoffNavProvider: wrap the nav and the hero in it, so the hero's HandoffNavCta can find the nav that catches it. One per page, or one per demo.