Skip to main content

UX Standards — 16: Command Palette (⌘K / Ctrl+K)

Governs: The keyboard-triggered command palette for navigation and search. Parent rules: See 00-OVERVIEW-AND-CSS-RULES.md and 02-HEADER.md first.


Overview

The Command Palette is a centered modal that opens when the user presses ⌘K (Mac) or Ctrl+K (Windows/Linux), or clicks the Search button in the header. It provides:

  • Instant navigation to any page in the application
  • Grouped navigation items matching the sidebar structure
  • Keyboard-first UX with arrow-key navigation and Enter to select

The palette is built using the shadcn/ui Command component (which wraps cmdk).


Trigger

Keyboard Shortcut

PlatformShortcut
Mac⌘K
Windows / LinuxCtrl+K

Header Search Button

The Search button in the header toolbar (visible in HeaderRight) opens the palette on click:

// Header search button (registers keyboard shortcut at app level)
<Button variant="ghost" size="icon" className="h-9 w-9"
onClick={() => setCommandOpen(true)}>
<Search className="h-5 w-5" />
</Button>

Tooltip: The header button tooltip shows "Search (⌘K)" (or "Search (Ctrl+K)" on Windows).

Keyboard Registration (App Level)

// App-level keyboard shortcut registration:
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setCommandOpen((open) => !open);
}
};
document.addEventListener('keydown', down);
return () => document.removeEventListener('keydown', down);
}, []);

Visual Anatomy

┌────────────────────────────────────────────────────────────┐
│ [🔍 Type a command or search... ] │ ← CommandInput
├────────────────────────────────────────────────────────────┤
│ Navigation │ ← CommandGroup heading
│ 📊 Dashboard │ ← CommandItem
│ 👥 Users │
│ 📋 Bookings │
├────────────────────────────────────────────────────────────┤
│ Settings │ ← CommandGroup heading
│ ⚙ General Settings │
│ 🎨 Appearance │
│ 👤 Profile │
├────────────────────────────────────────────────────────────┤
│ ↑↓ Navigate ↵ Select Esc Close │ ← Keyboard hints footer
└────────────────────────────────────────────────────────────┘

Container Spec

Built with shadcn/ui CommandDialog:

<CommandDialog open={commandOpen} onOpenChange={setCommandOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>

<CommandGroup heading="Navigation">
<CommandItem onSelect={() => { router.push('/dashboard'); setCommandOpen(false); }}>
<LayoutDashboard className="mr-2 h-4 w-4" />
Dashboard
</CommandItem>
{/* ... more items */}
</CommandGroup>

<CommandSeparator />

<CommandGroup heading="Settings">
<CommandItem onSelect={() => { router.push('/settings/appearance'); setCommandOpen(false); }}>
<Palette className="mr-2 h-4 w-4" />
Appearance
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
PropertyValue
ComponentCommandDialog (shadcn/ui — wraps cmdk)
PositionCentered (via Dialog backdrop)
Max widthmax-w-lg (shadcn default)
InputCommandInput placeholder="Type a command or search..."
Blocks pageYes — backdrop overlay, but keyboard focus stays in palette
CloseEsc key or click backdrop

The command palette groups items to match the sidebar navigation structure:

Group HeadingExample Items
NavigationDashboard, Users, Bookings, Products, Reports
SettingsGeneral Settings, Appearance, Profile, Notifications
(App-specific groups)Per-module navigation items

Each CommandGroup has a heading that displays as a small label above its items.


Command Item Pattern

<CommandItem
onSelect={() => {
router.push('/target-path');
setCommandOpen(false);
}}
>
<IconName className="mr-2 h-4 w-4" />
Label
</CommandItem>
ElementSpec
Iconh-4 w-4 mr-2
LabelPlain text — the page/section name
On selectNavigate + close palette

The modal shows keyboard hints at the bottom edge:

↑↓ Navigate ↵ Select Esc Close

These are displayed as text-xs text-muted-foreground in the command palette footer.


Empty State

When the search query has no matching items:

<CommandEmpty>No results found.</CommandEmpty>

Filtering Behavior

cmdk handles filtering automatically — items are filtered in real-time as the user types. The CommandInput value is matched against all CommandItem text content.


Violation Checklist

  • Triggered by ⌘K / Ctrl+K keyboard shortcut registered at app level
  • Header Search button has tooltip showing "Search (⌘K)" / "Search (Ctrl+K)"
  • Uses CommandDialog (not a custom modal)
  • Input placeholder: "Type a command or search..."
  • Items grouped with CommandGroup heading labels
  • Each item has a Lucide icon (h-4 w-4 mr-2)
  • Selecting an item navigates and closes the palette
  • CommandEmpty shows "No results found."
  • Keyboard hints visible: ↑↓ Navigate, ↵ Select, Esc Close