Command Palette
⌘K 喚起的命令面板,即時模糊搜尋、指令分組與鍵盤巡覽,高亮列以共享 layout 滑動。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add command-paletteCommand Palette 是用 React、Tailwind CSS 與 Motion 做的命令面板(⌘K command menu):按 ⌘K 或 Ctrl+K 開啟,輸入關鍵字即時過濾指令,用方向鍵與 Enter 執行,不需要額外的 combobox 套件。
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<CommandPalette/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
適用情境
- SaaS 後台的全域快速跳頁,例如「前往帳單」「開啟設定」。
- 文件站或知識庫的快速搜尋入口,列出常用頁面。
- 編輯器或儀表板中,把散落在選單裡的動作集中成一個鍵盤入口。
- 管理介面裡切換工作區、專案或主題這類頻繁操作。
安裝
npx webberui@latest add https://webberui.com/r/command-palette.json或在 components.json 設定 registries 後,改用 @webberui/command-palette 安裝。
使用
import * as React from "react";
import { Home, Plus } from "lucide-react";
import { CommandPalette } from "@/components/ui/command-palette";
export function Example() {
const [open, setOpen] = React.useState(false);
const groups = [
{
heading: "導覽",
items: [
{
id: "home",
label: "前往首頁",
icon: <Home className="size-4" />,
shortcut: "G H",
onSelect: () => console.log("home"),
},
],
},
{
heading: "動作",
items: [
{
id: "new",
label: "新增文件",
icon: <Plus className="size-4" />,
shortcut: "⌘ N",
onSelect: () => console.log("new"),
},
],
},
];
return <CommandPalette open={open} onOpenChange={setOpen} groups={groups} />;
}按下 ⌘K(macOS)或 Ctrl+K(Windows/Linux)即可隨時喚起面板;也可透過 open / onOpenChange 由外部按鈕控制。
範例
搭配 Next.js 路由跳頁
在 onSelect 裡呼叫 router.push;面板會先關閉再執行動作。外加一顆按鈕給不熟快捷鍵的使用者。
"use client";
import * as React from "react";
import { useRouter } from "next/navigation";
import { CreditCard, LayoutDashboard, Settings } from "lucide-react";
import { CommandPalette } from "@/components/ui/command-palette";
export function AppCommandMenu() {
const router = useRouter();
const [open, setOpen] = React.useState(false);
const groups = [
{
heading: "頁面",
items: [
{ id: "dashboard", label: "儀表板", icon: <LayoutDashboard className="size-4" />, onSelect: () => router.push("/dashboard") },
{ id: "billing", label: "帳單與付款", icon: <CreditCard className="size-4" />, onSelect: () => router.push("/settings/billing") },
{ id: "settings", label: "帳號設定", icon: <Settings className="size-4" />, shortcut: "⌘ ,", onSelect: () => router.push("/settings") },
],
},
];
return (
<>
<button onClick={() => setOpen(true)}>搜尋⋯ ⌘K</button>
<CommandPalette open={open} onOpenChange={setOpen} groups={groups} placeholder="搜尋頁面或指令⋯" />
</>
);
}非受控模式
不傳 open 時由元件自行管理開關,只靠 ⌘K / Ctrl+K 喚起;適合直接放在根 layout。
<CommandPalette
groups={[
{
heading: "外觀",
items: [
{ id: "light", label: "切換為淺色主題", onSelect: () => setTheme("light") },
{ id: "dark", label: "切換為深色主題", onSelect: () => setTheme("dark") },
],
},
]}
/>由資料產生分組
groups 是一般陣列,可以從 API 或 props 組出來,例如列出最近開啟的專案。
const groups = [
{
heading: "最近的專案",
items: recentProjects.map((project) => ({
id: `project-${project.id}`,
label: project.name,
onSelect: () => router.push(`/projects/${project.id}`),
})),
},
];Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
open | boolean | — | 受控模式的開啟狀態,不傳則由元件內部管理 |
onOpenChange | (open: boolean) => void | — | 開啟狀態變更時的回呼(受控與非受控皆會觸發) |
groups | CommandPaletteGroup[] | — | 指令分組資料,依序渲染每組標題與項目 |
placeholder | string | "輸入指令或搜尋⋯" | 搜尋框的 placeholder 文字 |
className | string | — | 追加到面板容器的 className |
CommandPaletteGroup
| 欄位 | 型別 | 說明 |
|---|---|---|
heading | string | 分組標題 |
items | CommandPaletteItem[] | 該分組的指令項目 |
CommandPaletteItem
| 欄位 | 型別 | 說明 |
|---|---|---|
id | string | 唯一識別碼,作為 option 的 DOM id 與 React key |
label | string | 顯示文字,同時是搜尋過濾的比對來源 |
icon | React.ReactNode | 選填的前導圖示 |
shortcut | string | 選填的快捷鍵提示,顯示在項目右側 |
onSelect | () => void | 選取(點擊或 Enter)此項目時執行的動作 |
細節
- 全域監聽
⌘K/Ctrl+K切換開關;面板以createPortal掛到document.body。 - 遮罩淡入、面板縮放淡入置中偏上;搜尋框自動聚焦,輸入即時做子序列模糊過濾。
- 上下鍵在過濾後的項目間循環移動高亮、
Enter執行、Esc關閉、點擊遮罩關閉。 - 高亮列的背景以共享
layoutId在項目間平滑滑動。
可及性
- 面板為
role="dialog"且aria-modal="true",開啟時鎖住 body 捲動、關閉後把焦點歸還給開啟前的元素。 - 搜尋框為
role="combobox",以aria-activedescendant指向目前高亮的項目;結果為role="listbox"、項目為role="option"並帶aria-selected。 - 使用者系統開啟「減少動態效果」時,面板僅以透明度淡入淡出、高亮列瞬間到位,不做縮放與滑動。
常見問題
可以在 Next.js App Router 使用嗎?
可以。原始碼開頭已有 "use client";面板用 createPortal 掛到 document.body,且在元件掛載後才渲染,伺服器端輸出為空,不會產生 hydration 不一致。
搜尋是怎麼比對的?
只比對每個項目的 label,採不分大小寫的子序列比對:查詢字元依序出現在文字中即命中,不需要連續。過濾後沒有項目的分組會隱藏,完全沒有結果時顯示「找不到符合的指令」。
選取項目後面板會自動關閉嗎?
會。點擊或按 Enter 時,元件先呼叫 onOpenChange(false) 關閉面板,再執行該項目的 onSelect。
鍵盤與螢幕閱讀器支援到什麼程度?
上下鍵循環移動高亮、Enter 執行、Esc 關閉。面板是 role="dialog" 並鎖住 body 捲動,搜尋框為 role="combobox" 並以 aria-activedescendant 指向高亮項目,關閉後焦點回到開啟前的元素。
需要安裝 cmdk 之類的套件嗎?
不需要。依賴只有 motion、lucide-react、clsx 與 tailwind-merge,過濾與鍵盤巡覽都寫在元件原始碼裡,可以從「手動」分頁直接複製。