導航
下拉選單
按鈕觸發的下拉選單:群組、分隔線、子選單、核取與單選項、快捷鍵標示,高亮列滑動、鍵盤與首字跳轉完整支援。
點按鈕展開的動作選單,適合使用者選單、列表列的「更多」操作、工具列的檢視切換。以複合元件組裝:DropdownMenu.Trigger 觸發、DropdownMenu.Content 承載項目,內含一般項目、群組標題、分隔線、子選單、核取與單選項,以及靠右的快捷鍵標示。浮層以 portal 掛到 body、依觸發鈕自動定位,放不下時翻到對側;高亮列在項目間滑動,方向鍵、Home/End、首字跳轉與子選單鍵盤操作都自己實作,不依賴任何 headless 套件。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add dropdown-menuPlayground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
100
150
編輯中的呼叫預設屬性
<DropdownMenu/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
安裝
npx webberui@latest add https://webberui.com/r/dropdown-menu.json或在 components.json 設定 registries 後,改用 @webberui/dropdown-menu 安裝。
使用
import { DropdownMenu } from "@/components/ui/dropdown-menu";
const [showStatusBar, setShowStatusBar] = React.useState(true);
const [panel, setPanel] = React.useState("bottom");
<DropdownMenu>
<DropdownMenu.Trigger>我的帳號</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" className="w-56">
<DropdownMenu.Label>林雨晴</DropdownMenu.Label>
<DropdownMenu.Item onSelect={() => console.log("個人資料")}>
個人資料
<DropdownMenu.Shortcut>⇧⌘P</DropdownMenu.Shortcut>
</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>邀請成員</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Item>電子郵件</DropdownMenu.Item>
<DropdownMenu.Item>複製連結</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Separator />
<DropdownMenu.CheckboxItem checked={showStatusBar} onCheckedChange={setShowStatusBar}>
顯示狀態列
</DropdownMenu.CheckboxItem>
<DropdownMenu.RadioGroup value={panel} onValueChange={setPanel}>
<DropdownMenu.RadioItem value="top">上</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="bottom">下</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
<DropdownMenu.Separator />
<DropdownMenu.Item variant="destructive">登出</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
open | boolean | — | 受控的開啟狀態;未提供時由元件內部管理 |
defaultOpen | boolean | false | 非受控模式的初始開啟狀態 |
onOpenChange | (open: boolean) => void | — | 開啟狀態變動時回呼 |
modal | boolean | true | 開啟時鎖住頁面捲動,外部點擊只關閉選單、不穿透到底下的元素 |
children | ReactNode | — | Trigger 與 Content |
DropdownMenu.Trigger
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
asChild | boolean | false | 為 true 時不渲染按鈕,把行為與 aria 屬性合併到唯一的子元素上 |
className | string | — | 透傳到觸發鈕;其餘 button 屬性(如 aria-label、disabled)也會透傳 |
DropdownMenu.Content
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
side | "top" | "right" | "bottom" | "left" | "bottom" | 相對觸發鈕的方位;放不下時自動翻到對側 |
align | "start" | "center" | "end" | "start" | 沿交叉軸的對齊方式 |
sideOffset | number | 4 | 與觸發鈕的距離(px) |
alignOffset | number | 0 | 沿交叉軸的額外偏移(px) |
collisionPadding | number | 8 | 與視窗邊緣保持的最小距離(px) |
duration | number | 100 | 進出場動畫時長(毫秒) |
loop | boolean | true | 方向鍵到頭尾時是否循環 |
subDelay | number | 150 | 指標停在子選單項目上多久後展開(毫秒),套用到所有子選單 |
className | string | — | 透傳到選單面板 |
DropdownMenu.Item
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
onSelect | () => void | — | 選取時回呼(點擊、Enter 或 Space) |
disabled | boolean | false | 停用:無法選取,鍵盤巡覽時跳過 |
inset | boolean | false | 左側留出與核取項目對齊的 icon 空間 |
variant | "default" | "destructive" | "default" | 外觀變體;destructive 用於刪除、登出等破壞性動作 |
textValue | string | — | typeahead 比對用文字;預設取項目的文字內容 |
closeOnSelect | boolean | true | 選取後是否關閉選單 |
className | string | — | 透傳到項目 |
DropdownMenu.CheckboxItem
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
checked | boolean | — | 受控的勾選狀態 |
defaultChecked | boolean | false | 非受控模式的初始勾選狀態 |
onCheckedChange | (checked: boolean) => void | — | 勾選狀態變動時回呼 |
onSelect | () => void | — | 選取時回呼 |
disabled | boolean | false | 停用 |
textValue | string | — | typeahead 比對用文字 |
closeOnSelect | boolean | false | 選取後是否關閉選單(核取項目預設不關閉) |
className | string | — | 透傳到項目 |
DropdownMenu.RadioGroup
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
value | string | — | 受控的選中值 |
defaultValue | string | — | 非受控模式的初始選中值 |
onValueChange | (value: string) => void | — | 選中值變動時回呼 |
className | string | — | 透傳到群組容器 |
DropdownMenu.RadioItem
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
value | string | — | 這個選項代表的值(必填) |
onSelect | () => void | — | 選取時回呼 |
disabled | boolean | false | 停用 |
textValue | string | — | typeahead 比對用文字 |
closeOnSelect | boolean | false | 選取後是否關閉選單(單選項目預設不關閉) |
className | string | — | 透傳到項目 |
DropdownMenu.Sub
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
open | boolean | — | 受控的子選單開啟狀態 |
defaultOpen | boolean | false | 非受控模式的初始開啟狀態 |
onOpenChange | (open: boolean) => void | — | 子選單開啟狀態變動時回呼 |
DropdownMenu.SubTrigger
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
disabled | boolean | false | 停用 |
inset | boolean | false | 左側留出與核取項目對齊的 icon 空間 |
textValue | string | — | typeahead 比對用文字 |
className | string | — | 透傳到項目 |
DropdownMenu.SubContent
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
sideOffset | number | -4 | 與 SubTrigger 的距離(px),負值讓子選單稍微疊上父選單 |
alignOffset | number | -5 | 沿交叉軸的偏移(px),預設讓第一項與 SubTrigger 對齊 |
collisionPadding | number | 8 | 與視窗邊緣保持的最小距離(px) |
loop | boolean | true | 方向鍵到頭尾時是否循環 |
className | string | — | 透傳到子選單面板 |
Label/Group/Separator/Shortcut
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
Label.inset | boolean | false | 群組標題(非互動);inset 時與核取項目文字對齊 |
Group | — | — | 以 role="group" 包住一組項目 |
Separator | — | — | role="separator" 的細分隔線 |
Shortcut | — | — | 靠右的灰色等寬小字;只是標示,實際快捷鍵由你自己綁定 |
所有子元件都接受 className,以 cn() 併入。
細節
- 定位:浮層以
position: fixed掛到body,依觸發鈕的位置計算;主軸放不下且對側空間較大時翻轉,交叉軸會推回視窗內並保留collisionPadding。視窗縮放、任何祖先捲動、觸發鈕或浮層尺寸變化都會以 requestAnimationFrame 節流重算;第一次量測完成前浮層保持隱藏,不會在左上角閃一下。翻轉後的實際方位寫在data-side/data-align上。 - 最大高度:浮層的
max-height等於落點方向的可用視窗高度,項目過多時內部捲動。 - 動畫:進場為淡入、從 95% 放大,並從 side 的反方向位移 8px 滑入;
transform-origin依實際落點設定(例:下方靠左展開時原點在左上)。時長由duration控制,同時寫入--wb-dropdown-menu-duration,浮層內所有 CSS 過渡都讀這個變數,可在全域覆寫。 - 高亮列:滑鼠移入或鍵盤聚焦時,背景以共享版面動畫在項目間滑動。
- 子選單:停留
subDelay毫秒後展開;指標從 SubTrigger 斜向移往子選單時,以「離開點+子選單兩角」構成的安全三角形判斷,途中掠過其他項目不會誤關子選單。 - typeahead:500ms 內連續輸入的字元累加成前綴比對
textValue(預設取項目文字、略過快捷鍵);同一個字母連打會在同字首項目間輪替,中文以首字比對。 modal預設開啟:開啟時鎖住body捲動(補上捲軸寬度避免版面跳動),並在後方放一層透明攔截層,外部點擊只關閉選單。設為false時頁面可捲動,點擊外部會關閉選單並讓被點的元素正常反應。
可及性
- 觸發鈕帶
aria-haspopup="menu"、aria-expanded與aria-controls;面板為role="menu"、aria-orientation="vertical",並以aria-labelledby指回觸發鈕 - 項目依類型使用
menuitem、menuitemcheckbox、menuitemradio,核取與單選項帶aria-checked;分隔線為role="separator",快捷鍵標示對輔助科技隱藏 - 鍵盤:觸發鈕上 Enter/Space/↓ 開啟並聚焦第一項、↑ 開啟並聚焦最後一項;選單內 ↑↓ 在非停用項間循環、Home/End 跳頭尾、Enter/Space 選取、Escape 關閉、Tab 關閉並讓焦點自然離開;子選單以 → 或 Enter 進入、← 或 Escape 返回
- 停用項目帶
aria-disabled,鍵盤巡覽時跳過;關閉選單後焦點回到觸發鈕 - typeahead 在中文等輸入法組字中(
isComposing或 keyCode 229)不觸發 - 使用者系統開啟「減少動態效果」時,浮層只保留淡入淡出(時長不變),不縮放、不位移;高亮列與單選圓點直接到位