home / skills / gwenwindflower / .charmschool / web-ux-command-palette

web-ux-command-palette skill

/agents/claude/skills/web-ux-command-palette

This skill helps you implement a keyboard-driven command palette using cmdk and shadcn/ui for fast, accessible navigation.

npx playbooks add skill gwenwindflower/.charmschool --skill web-ux-command-palette

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.4 KB
---
name: web-ux-command-palette
description: "Build keyboard-driven command palettes using cmdk + shadcn/ui. Use when: (1) Adding command palette to web apps, (2) Implementing keyboard navigation, (3) Building spotlight-style search, (4) Setting up cmdk with shadcn components."
---

# 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

## Core Components

| Component | Purpose |
| --- | --- |
| `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 |

## 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>
```

## 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);
}
```

## 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(' ')}`

## 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">
```

## 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

## External Resources

- cmdk docs: <https://cmdk.paco.me/>
- shadcn Command: <https://ui.shadcn.com/docs/components/command>
- Radix Dialog: <https://www.radix-ui.com/primitives/docs/components/dialog>

Overview

This skill teaches how to build keyboard-first command palettes using cmdk combined with shadcn/ui components. It focuses on creating accessible, performant, and fuzzy-searchable palettes that match Vercel/Linear/Raycast-style UX. The guidance covers core components, keyboard bindings, search indexing patterns, styling, and accessibility considerations.

How this skill works

The skill shows how to compose a modal CommandDialog with CommandInput, CommandList, CommandGroup, and CommandItem to present searchable actions. It wires global keyboard handlers (Cmd/Ctrl+K, /, Escape, arrows, Enter) to open, navigate, and select entries. It also explains search indexing approaches (static compile-time index or dynamic fetch) and how to include searchable text in each item’s value for cmdk’s fuzzy search.

When to use it

  • Add a global command palette or spotlight to a web app
  • Implement keyboard-first navigation for power users
  • Provide quick access to pages, actions, or developer commands
  • Replace slow or nested UI paths with a single-search entry point
  • Integrate cmdk with shadcn/ui and Radix primitives for accessible dialogs

Best practices

  • Expose Cmd/Ctrl+K and / as primary open shortcuts; ensure Escape closes the dialog
  • Include title, description, and keywords in the search index (value = title + ' ' + keywords) for reliable fuzzy matches
  • Group related actions with CommandGroup and separate with CommandSeparator for scanability
  • Keep CommandList height constrained and scrollable for long result sets
  • Ensure visible focus styles and aria-labels for icon-only items

Example use cases

  • Site-wide spotlight to navigate pages, posts, and settings with fuzzy search
  • App command launcher to run actions (toggle theme, open modal, execute scripts) via keyboard
  • Developer dotfiles UI to surface terminal commands, aliases, and scripts for quick copy/run
  • Admin dashboards where users jump between resources and actions without deep navigation
  • Documentation sites that let readers open pages and run in-page commands quickly

FAQ

How do I avoid opening the palette when typing in inputs?

Check the event target before opening: ignore '/' or Cmd/Ctrl+K when the focused element is an input, textarea, or select (use a helper like isInputFocused).

Should I build the search index at build time or fetch dynamically?

Use a static index for mostly-static content (best performance). Use a dynamic fetch or server action if content changes frequently or needs auth. You can also hybridize: static index for UI routes and dynamic fetch for recent items.

How do I surface keyboard shortcuts in items?

Use CommandShortcut to show hints (e.g., Enter or custom combos) and include them in the CommandItem layout. Keep visual hints concise and consistent.