web-ux-command-palette

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Command Palette Pattern

命令面板模式

Overview

概述

Build performant, keyboard-first command palettes using cmdk (the industry standard) with shadcn/ui components.
Why cmdk + shadcn:
  • De facto standard (Vercel, Linear, Raycast-style UIs)
  • Built-in fuzzy search
  • Accessible (Radix Dialog primitives)
  • Keyboard-first design
使用cmdk(行业标准工具)搭配shadcn/ui组件,构建高性能、优先支持键盘操作的命令面板。
选择cmdk + shadcn的原因:
  • 事实上的标准(适配Vercel、Linear、Raycast风格的UI)
  • 内置模糊搜索功能
  • 无障碍支持(基于Radix Dialog原语)
  • 优先键盘操作的设计

Core Components

核心组件

ComponentPurpose
CommandDialog
Modal wrapper (Dialog + Command)
CommandInput
Search input with icon
CommandList
Scrollable results container
CommandEmpty
"No results" state
CommandGroup
Categorized sections with headings
CommandItem
Individual selectable items
CommandSeparator
Visual divider between groups
CommandShortcut
Keyboard shortcut hint display
组件用途
CommandDialog
模态框包装器(Dialog + Command)
CommandInput
带图标的搜索输入框
CommandList
可滚动的结果容器
CommandEmpty
“无结果”状态展示
CommandGroup
带标题的分类区域
CommandItem
单个可选项目
CommandSeparator
分组间的视觉分隔线
CommandShortcut
键盘快捷键提示展示

Basic Structure

基础结构

tsx
<CommandDialog open={open} onOpenChange={setOpen}>
  <CommandInput placeholder="Type a command..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Navigation">
      <CommandItem onSelect={() => navigate('/home')}>
        <HomeIcon /> Home
      </CommandItem>
    </CommandGroup>
  </CommandList>
</CommandDialog>
tsx
<CommandDialog open={open} onOpenChange={setOpen}>
  <CommandInput placeholder="Type a command..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Navigation">
      <CommandItem onSelect={() => navigate('/home')}>
        <HomeIcon /> Home
      </CommandItem>
    </CommandGroup>
  </CommandList>
</CommandDialog>

Keyboard Shortcuts

键盘快捷键

Standard bindings:
  • Cmd+K
    /
    Ctrl+K
    - Open/close (standard convention)
  • /
    - Open (when not in input field)
  • Escape
    - Close
  • Arrow keys - Navigate
  • Enter
    - Select
Implementation:
tsx
useEffect(() => {
  const handler = (e: KeyboardEvent) => {
    if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
      e.preventDefault();
      setOpen(prev => !prev);
    }
    if (e.key === '/' && !isInputFocused(e.target)) {
      e.preventDefault();
      setOpen(true);
    }
  };
  document.addEventListener('keydown', handler);
  return () => document.removeEventListener('keydown', handler);
}, []);

function isInputFocused(target: EventTarget | null): boolean {
  return target instanceof HTMLElement &&
    ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName);
}
标准绑定:
  • Cmd+K
    /
    Ctrl+K
    - 打开/关闭(通用惯例)
  • /
    - 打开(未聚焦输入框时)
  • Escape
    - 关闭
  • 方向键 - 导航
  • Enter
    - 选择
实现代码:
tsx
useEffect(() => {
  const handler = (e: KeyboardEvent) => {
    if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
      e.preventDefault();
      setOpen(prev => !prev);
    }
    if (e.key === '/' && !isInputFocused(e.target)) {
      e.preventDefault();
      setOpen(true);
    }
  };
  document.addEventListener('keydown', handler);
  return () => document.removeEventListener('keydown', handler);
}, []);

function isInputFocused(target: EventTarget | null): boolean {
  return target instanceof HTMLElement &&
    ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName);
}

Search Index Patterns

搜索索引模式

Static sites:
  • Build index at compile time
  • Pass as props to component
Dynamic:
  • Fetch on mount or use server action
Structure:
typescript
interface SearchItem {
  title: string;
  description?: string;
  href: string;
  type: string;  // e.g., "page", "blog", "command"
  keywords?: string[];
}
cmdk search:
  • Searches the
    value
    prop on
    CommandItem
  • Include searchable text in value:
    value={title + ' ' + keywords?.join(' ')}
静态站点:
  • 在编译时构建索引
  • 作为props传递给组件
动态站点:
  • 在挂载时获取数据或使用服务器操作
索引结构:
typescript
interface SearchItem {
  title: string;
  description?: string;
  href: string;
  type: string;  // e.g., "page", "blog", "command"
  keywords?: string[];
}
cmdk搜索逻辑:
  • 搜索
    CommandItem
    上的
    value
    属性
  • 将可搜索文本包含在value中:
    value={title + ' ' + keywords?.join(' ')}

Styling Customization

样式自定义

Height:
tsx
<CommandList className="max-h-[300px]">
Dialog position:
  • Centered by default via
    DialogContent
Item states:
tsx
<CommandItem className="data-[selected=true]:bg-accent">
高度设置:
tsx
<CommandList className="max-h-[300px]">
对话框位置:
  • 默认通过
    DialogContent
    居中显示
项目状态样式:
tsx
<CommandItem className="data-[selected=true]:bg-accent">

Accessibility

无障碍支持

Built-in:
  • Focus trap (Radix Dialog)
  • Escape/click-outside handling
  • Arrow navigation (cmdk)
  • Screen reader announcements
Ensure:
  • Visible focus indicators
  • Meaningful item labels
  • Icon-only items have aria-label
内置特性:
  • 焦点陷阱(Radix Dialog)
  • 按Esc/点击外部关闭的处理
  • 方向键导航(cmdk)
  • 屏幕阅读器播报
需确保:
  • 可见的焦点指示器
  • 有意义的项目标签
  • 仅图标项目带有aria-label

External Resources

外部资源