Motion Navigation Menu

A spring-animated navigation menu with a single morphing container, layout-animated active pill, and direction-aware content transitions.

Installation

File Structure

highlight.tsx
motion-navigation-menu.tsx

Usage

import {
  MotionNavigationMenu,
  MotionNavigationMenuContent,
  MotionNavigationMenuIndicator,
  MotionNavigationMenuItem,
  MotionNavigationMenuLink,
  MotionNavigationMenuList,
  MotionNavigationMenuTrigger,
} from "@/components/unlumen-ui/motion-navigation-menu";

const listHighlightClassName = "bg-primary/10 rounded-lg";
const contentHighlightClassName =
  "bg-primary/10 rounded-lg ring-1 ring-primary/15";

export default function Example() {
  return (
    <MotionNavigationMenu
      viewportClassName="bg-surface border-none shadow-none"
      springStiffness={350}
      springDamping={32}
    >
      <MotionNavigationMenuList highlightClassName={listHighlightClassName}>
        <MotionNavigationMenuItem value="products">
          <MotionNavigationMenuTrigger>Products</MotionNavigationMenuTrigger>
          <MotionNavigationMenuContent
            highlightClassName={contentHighlightClassName}
          >
            <div className="grid w-[400px] grid-cols-2 gap-1">
              <MotionNavigationMenuLink href="/analytics">
                <span className="text-sm font-medium">Analytics</span>
                <span className="text-muted-foreground text-xs">
                  Understand your data flow.
                </span>
              </MotionNavigationMenuLink>
              <MotionNavigationMenuLink href="/automation">
                <span className="text-sm font-medium">Automation</span>
                <span className="text-muted-foreground text-xs">
                  Streamline workflows.
                </span>
              </MotionNavigationMenuLink>
            </div>
          </MotionNavigationMenuContent>
        </MotionNavigationMenuItem>

        <MotionNavigationMenuIndicator />

        <MotionNavigationMenuItem>
          <MotionNavigationMenuLink
            href="/pricing"
            className="px-4 py-2 text-sm font-medium"
          >
            Pricing
          </MotionNavigationMenuLink>
        </MotionNavigationMenuItem>
      </MotionNavigationMenuList>
    </MotionNavigationMenu>
  );
}

API Reference

MotionNavigationMenu

viewport?
boolean
true

When true, renders content inside the shared morphing viewport. When false, each content panel renders absolutely below its item.

viewportClassName?
string

Additional classes applied to the morphing viewport.

springBounce?
number
0

Bounce used by the spring transition for the viewport and content animations.

springStiffness?
number
350

Stiffness used by the spring transition for the viewport and content animations.

springDamping?
number
32

Damping used by the spring transition for the viewport and content animations.

value?
string

Controlled active item value. Use an empty string to close the menu.

onValueChange?
(value: string) => void

Callback fired when the active item value changes.

className?
string

Additional classes applied to the root element.

MotionNavigationMenuList

className?
string

Additional classes applied to the list element.

highlightClassName?
string

Additional classes applied to the hover highlight rendered behind trigger items.

MotionNavigationMenuItem

value?
string

Unique identifier for this item. Required when the item has a Trigger + Content pair.

className?
string

Additional classes applied to the list item.

MotionNavigationMenuTrigger

className?
string

Additional classes applied to the trigger button.

children?
React.ReactNode

Trigger content.

MotionNavigationMenuContent

Renders the panel shown when the parent item's trigger is active. Must be placed inside a MotionNavigationMenuItem that has a value prop. The content is portal-rendered into the morphing container, so its width drives the container's resize animation.

className?
string

Additional classes applied to the content wrapper.

highlightClassName?
string

Additional classes applied to the hover highlight rendered behind links inside the content.

innerClassName?
string

Additional classes applied to the inner content container.

MotionNavigationMenuLink

href?
string

Destination URL for the anchor.

className?
string

Additional classes applied to the link.

children?
React.ReactNode

Link content.

MotionNavigationMenuIndicator

className?
string

Additional classes applied to the indicator wrapper.

Notes

  • Shared viewport: with viewport enabled, open content is rendered inside one morphing viewport that animates width, height, opacity, and scale.
  • Direction-aware transitions: switching between menus slides content horizontally based on whether the new item is to the left or right of the previous one.
  • Custom highlights: use MotionNavigationMenuList's highlightClassName for trigger hover styling and MotionNavigationMenuContent's highlightClassName for dropdown link hover styling.
  • Controlled mode: pass value and onValueChange to control the active item from outside the component.
  • Overflow clamping: the dropdown automatically stays within its nearest clipping ancestor — no configuration needed when the menu is inside a constrained container like a preview panel or modal.

Credits

Built by Léo.

Keep in mind

Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.