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.
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>"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.
<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>
);
}Close after a link click
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
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | null | — | The expanded item in controlled mode; null means collapsed |
defaultValue | string | null | null | Initially 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 |
closeDelay | number | 150 | Milliseconds to wait before closing after the mouse leaves |
MegaMenuList
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Main navigation" | Accessible name for the navigation bar (nav) |
className | string | — | Appended to the navigation bar container's className |
MegaMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Unique identifier for this item (required) |
className | string | — | Appended to the item container's (li) className |
MegaMenuTrigger / MegaMenuContent
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Trigger content / panel content |
className | string | — | Appended className (for Content it applies to the content area) |
How it works
- A single morphing container: each
MegaMenuContentregisters its content into a shared collection at render time, and an internal Viewport renders them all in one container carryinglayout; when you switch items,popLayouttakes 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
navrather than a portal, so moving the mouse between the trigger and the panel never closes it by accident.
Accessibility
- The trigger is a native
buttoncarryingaria-haspopup,aria-expanded, and — while expanded —aria-controlspointing at the panel region (role="region"). - Keyboard operation:
Enter/Spacetoggles expansion;ArrowLeft/ArrowRightplusHome/Endmove between triggers (switching the panel too when one is already open);ArrowDownexpands and moves focus into the panel;Escapecloses 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.
Scroll-aware Navbar
A navigation bar that condenses, hides, or widens as you scroll — three variants sharing one consistent rhythm, with nested scroll container support.
Fullscreen Menu
A Nike-style fullscreen overlay menu — the backdrop unveils from the top down and the menu items slide up from under a mask in a staggered cascade.