導航

Command Palette

⌘K 喚起的命令面板,即時模糊搜尋、指令分組與鍵盤巡覽,高亮列以共享 layout 滑動。

把這個效果裝進你的專案

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

npx webberui@latest add command-palette

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

Command 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型別預設值說明
openboolean—受控模式的開啟狀態,不傳則由元件內部管理
onOpenChange(open: boolean) => void—開啟狀態變更時的回呼(受控與非受控皆會觸發)
groupsCommandPaletteGroup[]—指令分組資料,依序渲染每組標題與項目
placeholderstring"輸入指令或搜尋⋯"搜尋框的 placeholder 文字
classNamestring—追加到面板容器的 className

CommandPaletteGroup

欄位型別說明
headingstring分組標題
itemsCommandPaletteItem[]該分組的指令項目

CommandPaletteItem

欄位型別說明
idstring唯一識別碼,作為 option 的 DOM id 與 React key
labelstring顯示文字,同時是搜尋過濾的比對來源
iconReact.ReactNode選填的前導圖示
shortcutstring選填的快捷鍵提示,顯示在項目右側
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,過濾與鍵盤巡覽都寫在元件原始碼裡,可以從「手動」分頁直接複製。