導航

下拉選單

按鈕觸發的下拉選單:群組、分隔線、子選單、核取與單選項、快捷鍵標示,高亮列滑動、鍵盤與首字跳轉完整支援。

點按鈕展開的動作選單,適合使用者選單、列表列的「更多」操作、工具列的檢視切換。以複合元件組裝:DropdownMenu.Trigger 觸發、DropdownMenu.Content 承載項目,內含一般項目、群組標題、分隔線、子選單、核取與單選項,以及靠右的快捷鍵標示。浮層以 portal 掛到 body、依觸發鈕自動定位,放不下時翻到對側;高亮列在項目間滑動,方向鍵、Home/End、首字跳轉與子選單鍵盤操作都自己實作,不依賴任何 headless 套件。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add dropdown-menu

還沒有專案,或安裝遇到問題?看快速開始 →

Playground

即時調整 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型別預設值說明
openboolean—受控的開啟狀態;未提供時由元件內部管理
defaultOpenbooleanfalse非受控模式的初始開啟狀態
onOpenChange(open: boolean) => void—開啟狀態變動時回呼
modalbooleantrue開啟時鎖住頁面捲動,外部點擊只關閉選單、不穿透到底下的元素
childrenReactNode—Trigger 與 Content
欄位型別預設值說明
asChildbooleanfalse為 true 時不渲染按鈕,把行為與 aria 屬性合併到唯一的子元素上
classNamestring—透傳到觸發鈕;其餘 button 屬性(如 aria-label、disabled)也會透傳
欄位型別預設值說明
side"top" | "right" | "bottom" | "left""bottom"相對觸發鈕的方位;放不下時自動翻到對側
align"start" | "center" | "end""start"沿交叉軸的對齊方式
sideOffsetnumber4與觸發鈕的距離(px)
alignOffsetnumber0沿交叉軸的額外偏移(px)
collisionPaddingnumber8與視窗邊緣保持的最小距離(px)
durationnumber100進出場動畫時長(毫秒)
loopbooleantrue方向鍵到頭尾時是否循環
subDelaynumber150指標停在子選單項目上多久後展開(毫秒),套用到所有子選單
classNamestring—透傳到選單面板
欄位型別預設值說明
onSelect() => void—選取時回呼(點擊、Enter 或 Space)
disabledbooleanfalse停用:無法選取,鍵盤巡覽時跳過
insetbooleanfalse左側留出與核取項目對齊的 icon 空間
variant"default" | "destructive""default"外觀變體;destructive 用於刪除、登出等破壞性動作
textValuestring—typeahead 比對用文字;預設取項目的文字內容
closeOnSelectbooleantrue選取後是否關閉選單
classNamestring—透傳到項目
欄位型別預設值說明
checkedboolean—受控的勾選狀態
defaultCheckedbooleanfalse非受控模式的初始勾選狀態
onCheckedChange(checked: boolean) => void—勾選狀態變動時回呼
onSelect() => void—選取時回呼
disabledbooleanfalse停用
textValuestring—typeahead 比對用文字
closeOnSelectbooleanfalse選取後是否關閉選單(核取項目預設不關閉)
classNamestring—透傳到項目
欄位型別預設值說明
valuestring—受控的選中值
defaultValuestring—非受控模式的初始選中值
onValueChange(value: string) => void—選中值變動時回呼
classNamestring—透傳到群組容器
欄位型別預設值說明
valuestring—這個選項代表的值(必填)
onSelect() => void—選取時回呼
disabledbooleanfalse停用
textValuestring—typeahead 比對用文字
closeOnSelectbooleanfalse選取後是否關閉選單(單選項目預設不關閉)
classNamestring—透傳到項目
欄位型別預設值說明
openboolean—受控的子選單開啟狀態
defaultOpenbooleanfalse非受控模式的初始開啟狀態
onOpenChange(open: boolean) => void—子選單開啟狀態變動時回呼
欄位型別預設值說明
disabledbooleanfalse停用
insetbooleanfalse左側留出與核取項目對齊的 icon 空間
textValuestring—typeahead 比對用文字
classNamestring—透傳到項目
欄位型別預設值說明
sideOffsetnumber-4與 SubTrigger 的距離(px),負值讓子選單稍微疊上父選單
alignOffsetnumber-5沿交叉軸的偏移(px),預設讓第一項與 SubTrigger 對齊
collisionPaddingnumber8與視窗邊緣保持的最小距離(px)
loopbooleantrue方向鍵到頭尾時是否循環
classNamestring—透傳到子選單面板

Label/Group/Separator/Shortcut

欄位型別預設值說明
Label.insetbooleanfalse群組標題(非互動);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)不觸發
  • 使用者系統開啟「減少動態效果」時,浮層只保留淡入淡出(時長不變),不縮放、不位移;高亮列與單選圓點直接到位