覆蓋層與回饋

右鍵選單

在游標位置開啟的右鍵選單:子選單延遲展開、核取與單選項、快捷鍵標示,邊緣自動翻轉,觸控長按也能開啟。

在游標位置開啟的右鍵選單。在 Trigger 區塊按右鍵,選單就從游標點長出來;右側放不下會翻到游標左側、下方放不下會整塊往上推。提供一般項目、核取項、單選群組、群組標題、分隔線與快捷鍵標示,子選單在 hover 停留後從側邊滑入,並用「安全三角形」讓指標斜向移往子選單時不會誤關。鍵盤可完整操作(方向鍵、Enter、Escape、首字跳轉),觸控裝置長按 500ms 開啟。整組元件以 ContextMenu 搭配靜態屬性(ContextMenu.Item 等)組合,不依賴任何 headless 套件。

把這個效果裝進你的專案

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

npx webberui@latest add context-menu

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

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

100
150
編輯中的呼叫預設屬性
<ContextMenu/>

完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。

安裝

npx webberui@latest add https://webberui.com/r/context-menu.json

或在 components.json 設定 registries 後,改用 @webberui/context-menu 安裝。

使用

import { ContextMenu } from "@/components/ui/context-menu";

const [starred, setStarred] = React.useState(false);
const [view, setView] = React.useState("grid");

<ContextMenu onOpenChange={(open) => console.log(open)}>
  <ContextMenu.Trigger className="rounded-xl border p-4">
    2026 第四季預算.xlsx
  </ContextMenu.Trigger>
  <ContextMenu.Content>
    <ContextMenu.Item onSelect={() => console.log("開啟")}>
      開啟
      <ContextMenu.Shortcut>⌘O</ContextMenu.Shortcut>
    </ContextMenu.Item>
    <ContextMenu.Sub>
      <ContextMenu.SubTrigger>移動到</ContextMenu.SubTrigger>
      <ContextMenu.SubContent>
        <ContextMenu.Item>我的雲端硬碟</ContextMenu.Item>
        <ContextMenu.Item>團隊共享</ContextMenu.Item>
      </ContextMenu.SubContent>
    </ContextMenu.Sub>
    <ContextMenu.Separator />
    <ContextMenu.CheckboxItem checked={starred} onCheckedChange={setStarred}>
      加上星號
    </ContextMenu.CheckboxItem>
    <ContextMenu.Label inset>顯示方式</ContextMenu.Label>
    <ContextMenu.RadioGroup value={view} onValueChange={setView}>
      <ContextMenu.RadioItem value="grid">圖示</ContextMenu.RadioItem>
      <ContextMenu.RadioItem value="list">清單</ContextMenu.RadioItem>
    </ContextMenu.RadioGroup>
    <ContextMenu.Separator />
    <ContextMenu.Item variant="destructive">移到垃圾桶</ContextMenu.Item>
  </ContextMenu.Content>
</ContextMenu>

Props

ContextMenu

Prop型別預設值說明
onOpenChange(open: boolean) => void—開啟/關閉時回呼。開啟位置來自右鍵事件,因此不提供受控的 open
modalbooleantrue模態模式:開啟時攔截選單外的指標操作,點外面只會關閉選單、不會觸發底下的元素
childrenReactNode—ContextMenu.Trigger 與 ContextMenu.Content

ContextMenu.Trigger

欄位型別預設值說明
asChildbooleanfalse把行為併入唯一的子元素,而不是額外包一層 div
disabledbooleanfalse停用時不攔截右鍵,瀏覽器原生選單照常出現
classNamestring—併入觸發區塊;開啟時帶有 data-state="open" 可用來加樣式

其餘 HTML 屬性(aria-label、tabIndex 等)會透傳到觸發區塊;預設 tabIndex 為 0,讓鍵盤能聚焦後按 Shift+F10 開啟。

ContextMenu.Content

欄位型別預設值說明
durationnumber100進出場動畫時長(ms),同時寫入 --wb-context-menu-duration
subDelaynumber150子選單 hover 停留多久後展開(ms)
collisionPaddingnumber8與視窗邊緣保留的距離(px),放不下時翻轉或推移
loopbooleantrue↑/↓ 到頭尾時是否循環
aria-labelstring—選單的無障礙名稱
classNamestring—併入選單面板

ContextMenu.Item

欄位型別預設值說明
onSelect(event: Event) => void—選取時回呼;呼叫 event.preventDefault() 可讓選單保持開啟
disabledbooleanfalse停用:設定 aria-disabled,並在方向鍵巡覽時略過
closeOnSelectbooleantrue選取後是否關閉選單
textValuestring—首字跳轉比對用文字;預設取 children 的文字(略過 Shortcut)
variant"default" | "destructive""default"destructive 為紅色的危險操作
insetbooleanfalse左側留出指示器寬度,與核取/單選項對齊
classNamestring—併入項目

ContextMenu.CheckboxItem

欄位型別預設值說明
checkedboolean—受控的勾選狀態
defaultCheckedbooleanfalse非受控模式的初始勾選狀態
onCheckedChange(checked: boolean) => void—勾選狀態改變時回呼
closeOnSelectbooleanfalse選取後是否關閉選單
onSelect、disabled、textValue、className——同 ContextMenu.Item

ContextMenu.RadioGroup/RadioItem

欄位型別預設值說明
valuestring—RadioGroup:受控的選取值;RadioItem:此選項的值(必填)
defaultValuestring—RadioGroup:非受控模式的初始選取值
onValueChange(value: string) => void—RadioGroup:選取值改變時回呼
closeOnSelectbooleanfalseRadioItem:選取後是否關閉選單

其他

欄位型別預設值說明
ContextMenu.Labelinset?、className?—群組標題,非互動
ContextMenu.Grouparia-label?、className?—role="group" 的分組容器
ContextMenu.SeparatorclassName?—role="separator" 的分隔線
ContextMenu.ShortcutclassName?—靠右的灰色等寬小字,只是標示,快捷鍵本身請自行綁定
ContextMenu.Subchildren—包住 SubTrigger 與 SubContent
ContextMenu.SubTriggerdisabled?、textValue?、inset?、className?—開啟子選單的項目,右側自動加上箭頭
ContextMenu.SubContentclassName?—子選單面板,以 SubTrigger 為錨點

細節

  • 定位:主選單的錨點是游標位置的 0×0 虛擬矩形,預設放在游標右下(side 右、align 起點、間距 2px)。右側放不下翻到左側,下方放不下整塊往上推;transform-origin 跟著游標相對浮層的位置,翻轉後依然從游標點長出來。子選單以 SubTrigger 為錨點,右側放不下翻到左側。浮層 portal 到 body、position: fixed,並在視窗縮放、任何容器捲動與浮層尺寸變化時重新計算。
  • 已開啟時在同一個 Trigger 內再按右鍵,選單會直接移到新位置,不重播完整的關閉+開啟。
  • 觸控:pointerType 為 touch 的按壓持續 500ms 開啟;移動超過 10px 或抬起就取消。
  • 動畫:進出場為淡入 + scale(.95) + 沿方向反側 8px 位移;duration 驅動動畫並寫入 --wb-context-menu-duration,面板內的轉場都讀這個變數,可在全域覆寫。高亮背景以共享版面動畫在項目間滑動。
  • 子選單:hover 停留 subDelay 後展開;指標從 SubTrigger 離開時,以離開點與子選單靠近側兩角構成的三角形為安全區,指標在區內移動時不會因經過其他項目而關閉子選單。
  • 首字跳轉:500ms 內連續輸入的字元累加成前綴比對 textValue,中文以首字比對;同一個字元連按會在同字首項目間輪轉。輸入法組字中的按鍵一律略過。

可及性

  • Content 為 role="menu"、aria-orientation="vertical";項目依種類為 menuitem、menuitemcheckbox、menuitemradio,核取/單選項帶 aria-checked
  • 開啟後焦點移到選單本身(不自動高亮第一項),按 ↓ 從第一項開始;↑/↓ 在非停用項間循環,Home/End 跳頭尾,Enter/Space 選取
  • Escape 關閉並把焦點交回 Trigger;Tab 關閉選單,焦點從 Trigger 自然移往下一個元素
  • SubTrigger 帶 aria-haspopup="menu" 與 aria-expanded;→ 或 Enter 開啟並聚焦子選單第一項,← 或 Escape 只關閉子選單並回到 SubTrigger
  • Trigger 聚焦時按 Shift+F10 或選單鍵,會在 Trigger 中心開啟選單
  • 停用項設定 aria-disabled 並在巡覽時略過;Shortcut 標示對輔助科技隱藏;分隔線為 role="separator"
  • 使用者系統開啟「減少動態效果」時,進出場只保留淡入(時長不變),不縮放、不位移;高亮背景瞬間到位