Navigation

Mega Menu

A large dropdown navigation menu whose panel height morphs smoothly to fit as you switch items, with hover and keyboard support.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/mega-menu.en.json?t=<install token>"

New project or installation trouble? Start here →

Mega Menu is a React mega menu navigation component built with Tailwind CSS and Motion. You compose it from compound components; hovering a trigger opens it, and when you switch items a single panel container smoothly resizes its height and width to fit the new content.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

150
Active edited usageDefault props
<MegaMenu/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • Top navigation for a SaaS or company site where "Products", "Solutions" and "Resources" each need several columns of links.
  • Category menus on a store, with subcategories and a featured image in one panel.
  • Docs sites or developer portals that group API, SDK and guide links.
  • Not a good fit: a mobile hamburger or full-screen navigation, use Fullscreen Menu; for a few items without dropdown panels, see Pill Nav.

Installation

npx webberui@latest add "https://webberui.com/r/mega-menu.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/mega-menu.

Usage

Assemble it from compound components. Every MegaMenuItem needs a unique value and contains one MegaMenuTrigger and one MegaMenuContent.

import {
  MegaMenu,
  MegaMenuList,
  MegaMenuItem,
  MegaMenuTrigger,
  MegaMenuContent,
} from "@/components/ui/mega-menu";

<MegaMenu align="center">
  <MegaMenuList label="Main navigation">
    <MegaMenuItem value="products">
      <MegaMenuTrigger>Products</MegaMenuTrigger>
      <MegaMenuContent>{/* Large panel content */}</MegaMenuContent>
    </MegaMenuItem>

    <MegaMenuItem value="resources">
      <MegaMenuTrigger>Resources</MegaMenuTrigger>
      <MegaMenuContent>{/* Another set of content, possibly a different height */}</MegaMenuContent>
    </MegaMenuItem>
  </MegaMenuList>
</MegaMenu>

When panels for different items have different sizes, the container morphs its height and width automatically as you switch, instead of jumping instantly.

controlled mode

Pass value (the expanded item's value, or null for collapsed) and onValueChange to take over the open state; when omitted, the component manages it internally.

const [open, setOpen] = useState<string | null>(null);

<MegaMenu value={open} onValueChange={setOpen}>
  {/* ... */}
</MegaMenu>

Examples

Multi-column product panel

MegaMenuContent accepts any layout. This one uses two columns of links with descriptions; className applies to the panel content area.

import {
  MegaMenu,
  MegaMenuList,
  MegaMenuItem,
  MegaMenuTrigger,
  MegaMenuContent,
} from "@/components/ui/mega-menu";

const products = [
  { href: "/analytics", title: "Analytics", desc: "Live traffic and conversions" },
  { href: "/automation", title: "Automation", desc: "Scheduled emails and triggers" },
  { href: "/crm", title: "CRM", desc: "Contacts and deal history in one place" },
  { href: "/integrations", title: "Integrations", desc: "Connect your third-party tools" },
];

export function SiteNav() {
  return (
    <MegaMenu>
      <MegaMenuList label="Main navigation">
        <MegaMenuItem value="products">
          <MegaMenuTrigger>Products</MegaMenuTrigger>
          <MegaMenuContent className="grid w-[32rem] grid-cols-2 gap-2">
            {products.map((p) => (
              <a key={p.href} href={p.href} className="rounded-lg p-3 hover:bg-neutral-100 dark:hover:bg-neutral-800">
                <p className="text-sm font-medium">{p.title}</p>
                <p className="text-xs text-neutral-500">{p.desc}</p>
              </a>
            ))}
          </MegaMenuContent>
        </MegaMenuItem>
        <MegaMenuItem value="pricing">
          <MegaMenuTrigger>Plans</MegaMenuTrigger>
          <MegaMenuContent className="w-64">
            <a href="/pricing" className="block rounded-lg p-3 text-sm hover:bg-neutral-100 dark:hover:bg-neutral-800">Compare all plans</a>
          </MegaMenuContent>
        </MegaMenuItem>
      </MegaMenuList>
    </MegaMenu>
  );
}

Links inside the panel do not close the menu on their own. With Next.js Link doing client-side navigation, switch to controlled mode and set value to null on click.

"use client";

import * as React from "react";
import Link from "next/link";

export function DocsNav() {
  const [open, setOpen] = React.useState<string | null>(null);

  return (
    <MegaMenu value={open} onValueChange={setOpen} align="end">
      <MegaMenuList label="Docs navigation">
        <MegaMenuItem value="guides">
          <MegaMenuTrigger>Guides</MegaMenuTrigger>
          <MegaMenuContent className="flex w-56 flex-col">
            <Link href="/docs/quick-start" onClick={() => setOpen(null)}>Quick start</Link>
            <Link href="/docs/deploy" onClick={() => setOpen(null)}>Deploy to production</Link>
          </MegaMenuContent>
        </MegaMenuItem>
      </MegaMenuList>
    </MegaMenu>
  );
}

Tune the close delay

closeDelay sets how long the menu waits after the pointer leaves; if the panel sits far from the triggers or users tend to overshoot, raise it to around 300 ms.

<MegaMenu align="center" closeDelay={300}>
  {/* ... */}
</MegaMenu>

Props

MegaMenu

PropTypeDefaultDescription
valuestring | null—The expanded item in controlled mode; null means collapsed
defaultValuestring | nullnullInitially expanded item in uncontrolled mode
onValueChange(value: string | null) => void—Callback fired when the expanded item changes
align"start" | "center" | "end""start"Horizontal alignment of the panel relative to the navigation bar
closeDelaynumber150Milliseconds to wait before closing after the mouse leaves

MegaMenuList

PropTypeDefaultDescription
labelstring"Main navigation"Accessible name for the navigation bar (nav)
classNamestring—Appended to the navigation bar container's className

MegaMenuItem

PropTypeDefaultDescription
valuestring—Unique identifier for this item (required)
classNamestring—Appended to the item container's (li) className

MegaMenuTrigger / MegaMenuContent

PropTypeDefaultDescription
childrenReactNode—Trigger content / panel content
classNamestring—Appended className (for Content it applies to the content area)

How it works

  • A single morphing container: each MegaMenuContent registers its content into a shared collection at render time, and an internal Viewport renders them all in one container carrying layout; when you switch items, popLayout takes the exiting panel out of the layout flow so the container immediately measures the new content and tweens its height.
  • Hover intent: moving the mouse onto a trigger expands it, and leaving the navigation bar only closes it after closeDelay, which leaves a buffer for moving onto the panel; moving back in cancels the close.
  • The panel sits right under the navigation bar: it is an absolutely positioned child inside the nav rather than a portal, so moving the mouse between the trigger and the panel never closes it by accident.

Accessibility

  • The trigger is a native button carrying aria-haspopup, aria-expanded, and — while expanded — aria-controls pointing at the panel region (role="region").
  • Keyboard operation: Enter / Space toggles expansion; ArrowLeft / ArrowRight plus Home / End move between triggers (switching the panel too when one is already open); ArrowDown expands and moves focus into the panel; Escape closes it and focus returns to the trigger.
  • Clicking outside the navigation bar closes the panel.
  • When the user has "reduce motion" enabled at the system level, the height morph along with the displacement and scaling are disabled, leaving only a brief fade in and out.

FAQ

Does it work with the Next.js App Router?

Yes. The source starts with "use client", so import it from a Server Component layout or page as usual; next/link works inside the panels.

How does it behave on touch devices?

Hover-to-open only applies to mouse pointers. Touch and pen users tap a trigger to open and close it, and tapping outside the nav closes the panel.

How do I control the panel width?

The panel container is w-max with a minimum of 16rem and a maximum of min(90vw, 44rem), so your content sets the width. Put widths or grids in the MegaMenuContent className, and use align to anchor the panel to the start, center or end of the nav.

Which keys are supported?

Enter / Space toggle an item, Left / Right arrows and Home / End move between triggers, ArrowDown opens and moves focus into the panel, and Escape closes and returns focus to the trigger.

What happens with reduced motion?

With "reduce motion" enabled, the component turns off the height morph and the offset and scale animation, leaving only a short fade.