Mega Menu
大型下拉導覽選單,切換項目時面板高度自適應平滑 morph,支援懸停與鍵盤操作。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add "https://webberui.com/r/mega-menu.json?t=<安裝 token>"Mega Menu 是用 React、Tailwind CSS 與 Motion 做的大型下拉選單(mega menu)導覽元件:以 compound components 組合,滑鼠移入觸發鈕即展開,切換項目時同一個面板容器會依內容平滑調整高度與寬度。
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<MegaMenu/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
適用情境
- SaaS 或企業官網頂部導覽,「產品」「解決方案」「資源」各自需要多欄連結。
- 電商網站的商品分類選單,一個面板裡放子分類與主打圖片。
- 文件站或開發者入口,把 API、SDK、教學分組呈現。
- 不適合:行動版漢堡選單或全螢幕導覽,改用 Fullscreen Menu;項目少、不需要下拉面板時,可以看 Pill Nav。
安裝
npx webberui@latest add "https://webberui.com/r/mega-menu.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/mega-menu 安裝。
使用
以 compound components 組合。每個 MegaMenuItem 需帶唯一的 value,並包含一個 MegaMenuTrigger 與一個 MegaMenuContent。
import {
MegaMenu,
MegaMenuList,
MegaMenuItem,
MegaMenuTrigger,
MegaMenuContent,
} from "@/components/ui/mega-menu";
<MegaMenu align="center">
<MegaMenuList label="主導覽">
<MegaMenuItem value="products">
<MegaMenuTrigger>產品</MegaMenuTrigger>
<MegaMenuContent>{/* 大型面板內容 */}</MegaMenuContent>
</MegaMenuItem>
<MegaMenuItem value="resources">
<MegaMenuTrigger>資源</MegaMenuTrigger>
<MegaMenuContent>{/* 另一組內容,高度可不同 */}</MegaMenuContent>
</MegaMenuItem>
</MegaMenuList>
</MegaMenu>不同項目的面板內容尺寸不同時,切換時容器會自動 morph 高度與寬度,而非瞬間跳動。
受控模式
傳入 value(展開的項目 value,null 為收合)與 onValueChange 即可接管開闔狀態;不傳則由元件內部管理。
const [open, setOpen] = useState<string | null>(null);
<MegaMenu value={open} onValueChange={setOpen}>
{/* ... */}
</MegaMenu>範例
多欄產品面板
MegaMenuContent 的內容可以是任何排版,這裡用兩欄連結加說明文字;className 會套用到面板內容區。
import {
MegaMenu,
MegaMenuList,
MegaMenuItem,
MegaMenuTrigger,
MegaMenuContent,
} from "@/components/ui/mega-menu";
const products = [
{ href: "/analytics", title: "數據分析", desc: "即時查看流量與轉換" },
{ href: "/automation", title: "自動化流程", desc: "排程寄信與觸發條件" },
{ href: "/crm", title: "客戶管理", desc: "整合聯絡人與交易紀錄" },
{ href: "/integrations", title: "整合服務", desc: "串接常用的第三方工具" },
];
export function SiteNav() {
return (
<MegaMenu>
<MegaMenuList label="主導覽">
<MegaMenuItem value="products">
<MegaMenuTrigger>產品</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>方案</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">比較所有方案</a>
</MegaMenuContent>
</MegaMenuItem>
</MegaMenuList>
</MegaMenu>
);
}點擊連結後收合
面板內的連結不會自動關閉選單。在 Next.js 用 Link 做用戶端跳頁時,可以改用受控模式,在點擊後把 value 設為 null。
"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="文件導覽">
<MegaMenuItem value="guides">
<MegaMenuTrigger>教學</MegaMenuTrigger>
<MegaMenuContent className="flex w-56 flex-col">
<Link href="/docs/quick-start" onClick={() => setOpen(null)}>快速開始</Link>
<Link href="/docs/deploy" onClick={() => setOpen(null)}>部署到正式環境</Link>
</MegaMenuContent>
</MegaMenuItem>
</MegaMenuList>
</MegaMenu>
);
}調整關閉延遲
closeDelay 決定滑鼠離開後多久才收合;面板與觸發鈕距離較遠或使用者容易滑出時,可以拉長到 300 毫秒左右。
<MegaMenu align="center" closeDelay={300}>
{/* ... */}
</MegaMenu>Props
MegaMenu
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
value | string | null | — | 受控模式的展開項目,null 為收合 |
defaultValue | string | null | null | 非受控模式的初始展開項目 |
onValueChange | (value: string | null) => void | — | 展開項目變更時的回呼 |
align | "start" | "center" | "end" | "start" | 面板相對導覽列的水平對齊 |
closeDelay | number | 150 | 滑鼠移出後延遲關閉的毫秒數 |
MegaMenuList
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
label | string | "主導覽" | 導覽列(nav)的無障礙名稱 |
className | string | — | 追加到導覽列容器的 className |
MegaMenuItem
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
value | string | — | 此項目的唯一識別(必填) |
className | string | — | 追加到項目容器(li)的 className |
MegaMenuTrigger / MegaMenuContent
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | ReactNode | — | 觸發鈕內容 / 面板內容 |
className | string | — | 追加的 className(Content 套用於內容區) |
細節
- 單一容器 morph:各
MegaMenuContent於 render 時把內容登記到共享集合,由內部 Viewport 集中渲染於同一個帶layout的容器;切換項目時以popLayout讓離場面板脫離排版流,容器立即依新內容量測並補間高度。 - 懸停意圖:滑鼠移入觸發鈕即展開,移出導覽列後延遲
closeDelay才關閉,留出移動到面板的緩衝;再次移入會取消關閉。 - 面板即在導覽列下方:面板為
nav內的絕對定位子節點,不使用 portal,滑鼠在觸發鈕與面板之間移動不會誤觸關閉。
可及性
- 觸發鈕為原生
button,帶aria-haspopup、aria-expanded與展開時的aria-controls,指向面板區域(role="region")。 - 鍵盤操作:
Enter/Space切換展開;ArrowLeft/ArrowRight與Home/End於觸發鈕之間移動(面板已開時同步切換);ArrowDown展開並將焦點移入面板;Escape關閉並將焦點歸還觸發鈕。 - 點擊導覽列以外區域會關閉面板。
- 使用者系統開啟「減少動態效果」時,停用高度 morph 與位移縮放,僅保留短暫淡入淡出。
常見問題
可以在 Next.js App Router 使用嗎?
可以。原始碼開頭已有 "use client",在 Server Component 的 layout 或頁面直接 import 即可;面板內可以放 next/link。
觸控裝置上怎麼操作?
懸停展開只對滑鼠指標生效,觸控與觸控筆改以點擊觸發鈕開關。點擊導覽列以外的區域會關閉面板。
面板寬度怎麼控制?
面板容器是 w-max,最小 16rem、最大 min(90vw, 44rem),實際寬度由你的內容決定;在 MegaMenuContent 的 className 設定寬度或格線即可。用 align 決定面板靠左、置中或靠右對齊導覽列。
鍵盤操作支援哪些按鍵?
Enter/Space 切換展開,左右方向鍵與 Home/End 在觸發鈕之間移動,ArrowDown 展開並把焦點移入面板,Escape 關閉並把焦點還給觸發鈕。
減少動態效果時會怎樣?
系統開啟「減少動態效果」時,元件停用高度 morph 與位移縮放,只保留短暫的淡入淡出。