|$ curl https://forge-ai.dev/api/markdown?path=docs/components/command-palette
$cat docs/command-palette.md
updated Recently·14 min read·published

Command Palette

CSSHTMLUISearchKeyboardAdvanced🎯Free Tools

Command palettes are power-user interfaces triggered by keyboard shortcuts (Cmd+K, Ctrl+K). They provide fast access to actions, navigation, search, and settings without leaving the keyboard. Inspired by VS Code, Raycast, Linear, and Figma — the gold standard for developer and designer workflows.

Basic Command Palette

A minimal command palette with a search input, scrollable results list, keyboard shortcut badges, and an ESC hint. The overlay backdrop focuses attention on the search and dismisses on click or Escape key.

basic-command-palette
Live
untitled.html
HTML
1<div class="cp-demo">
2 <button class="cp-trigger" id="cp-open">
3 Search commands...
4 <span class="cp-shortcut">
5 ⌘K
6 </span>
7 </button>
8 <div class="cp-overlay" id="cp-overlay">
9 <div class="cp-modal">
10 <div class="cp-input-wrap">
11 <svg class="cp-search-icon" width="16" height="16" viewBox="0 0 24 24"
12 fill="none" stroke="currentColor" stroke-width="2"
13 stroke-linecap="round" stroke-linejoin="round">
14 <circle cx="11" cy="11" r="8"/>
15 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
16 </svg>
17 <input type="text" class="cp-input"
18 placeholder="Type a command or search..."
19 id="cp-input" autofocus />
20 <span class="cp-esc">
21 ESC
22 </span>
23 </div>
24 <div class="cp-results">
25 <div class="cp-group">
26 <span class="cp-group-label">
27 Suggestions
28 </span>
29 <button class="cp-item active">
30 <span class="cp-item-icon">
31 📄
32 </span>
33 <span class="cp-item-text">
34 Create new document
35 </span>
36 <span class="cp-item-badge">
37 ⌘N
38 </span>
39 </button>
40 <button class="cp-item">
41 <span class="cp-item-icon">
42 📁
43 </span>
44 <span class="cp-item-text">
45 Open project
46 </span>
47 <span class="cp-item-badge">
48 ⌘O
49 </span>
50 </button>
51 <button class="cp-item">
52 <span class="cp-item-icon">
53 🔍
54 </span>
55 <span class="cp-item-text">
56 Search files
57 </span>
58 <span class="cp-item-badge">
59 ⌘P
60 </span>
61 </button>
62 </div>
63 <div class="cp-group">
64 <span class="cp-group-label">
65 Actions
66 </span>
67 <button class="cp-item">
68 <span class="cp-item-icon">
69
70 </span>
71 <span class="cp-item-text">
72 Open settings
73 </span>
74 <span class="cp-item-badge">
75 ⌘,
76 </span>
77 </button>
78 <button class="cp-item">
79 <span class="cp-item-icon">
80 🎨
81 </span>
82 <span class="cp-item-text">
83 Toggle theme
84 </span>
85 <span class="cp-item-badge">
86 ⌘D
87 </span>
88 </button>
89 <button class="cp-item">
90 <span class="cp-item-icon">
91 📋
92 </span>
93 <span class="cp-item-text">
94 Show clipboard
95 </span>
96 <span class="cp-item-badge">
97 ⌘V
98 </span>
99 </button>
100 </div>
101 </div>
102 </div>
103 </div>
104</div>
preview
Grouped Results with Highlight

Results organized by category with matched text highlighting. The search term "de" highlights matching substrings in green. Groups help users scan quickly and understand the type of each result.

grouped-command-palette
Live
untitled.html
HTML
1<div class="cp-demo2">
2 <div class="cp-modal2">
3 <div class="cp-input-wrap2">
4 <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
5 stroke="currentColor" stroke-width="2" stroke-linecap="round"
6 stroke-linejoin="round">
7 <circle cx="11" cy="11" r="8"/>
8 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
9 </svg>
10 <input type="text" class="cp-input2" placeholder="Search..."
11 value="de" readonly />
12 <span class="cp-count">
13 4 results
14 </span>
15 </div>
16 <div class="cp-results2">
17 <div class="cp-group2">
18 <span class="cp-group-label2">
19 Navigation
20 </span>
21 <button class="cp-item2">
22 <span class="cp-icon2" style="color:#3B82F6">
23
24 </span>
25 <span class="cp-text2">
26 Dashboard
27 </span>
28 <span class="cp-desc2">
29 Main overview page
30 </span>
31 </button>
32 <button class="cp-item2 active">
33 <span class="cp-icon2" style="color:#3B82F6">
34
35 </span>
36 <span class="cp-text2">
37 <mark>
38 De
39 </mark>
40 ployments
41 </span>
42 <span class="cp-desc2">
43 View all deployments
44 </span>
45 </button>
46 </div>
47 <div class="cp-group2">
48 <span class="cp-group-label2">
49 Actions
50 </span>
51 <button class="cp-item2">
52 <span class="cp-icon2" style="color:#00FF41">
53
54 </span>
55 <span class="cp-text2">
56 <mark>
57 De
58 </mark>
59 lete project
60 </span>
61 <span class="cp-desc2">
62 Permanently remove
63 </span>
64 </button>
65 <button class="cp-item2">
66 <span class="cp-icon2" style="color:#F97316">
67
68 </span>
69 <span class="cp-text2">
70 <mark>
71 De
72 </mark>
73 activate env
74 </span>
75 <span class="cp-desc2">
76 Disable environment
77 </span>
78 </button>
79 </div>
80 </div>
81 </div>
82</div>
preview
Recent Actions

Showing recently used actions at the top of an empty search helps repeat workflows faster. Recent items persist in localStorage and fade as the user types to filter. Time-since labels (e.g., "2m ago") reinforce recency.

recent-command-palette
Live
untitled.html
HTML
1<div class="cp-demo3">
2 <div class="cp-modal3">
3 <div class="cp-input-wrap3">
4 <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
5 stroke="currentColor" stroke-width="2" stroke-linecap="round"
6 stroke-linejoin="round">
7 <circle cx="11" cy="11" r="8"/>
8 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
9 </svg>
10 <input type="text" class="cp-input3" placeholder="Type a command..." />
11 <span class="cp-esc3">
12 ESC
13 </span>
14 </div>
15 <div class="cp-results3">
16 <div class="cp-group3">
17 <span class="cp-group-label3">
18 <svg width="12" height="12" viewBox="0 0 24 24" fill="none"
19 stroke="currentColor" stroke-width="2" stroke-linecap="round"
20 stroke-linejoin="round">
21 <circle cx="12" cy="12" r="10"/>
22 <polyline points="12 6 12 12 16 14"/>
23 </svg>
24 Recent
25 </span>
26 <button class="cp-item3">
27 <span class="cp-icon3">
28 🚀
29 </span>
30 <span class="cp-text3">
31 Deploy to production
32 </span>
33 <span class="cp-time3">
34 2m ago
35 </span>
36 </button>
37 <button class="cp-item3">
38 <span class="cp-icon3">
39 🔄
40 </span>
41 <span class="cp-text3">
42 Restart dev server
43 </span>
44 <span class="cp-time3">
45 15m ago
46 </span>
47 </button>
48 <button class="cp-item3">
49 <span class="cp-icon3">
50 📦
51 </span>
52 <span class="cp-text3">
53 Build for staging
54 </span>
55 <span class="cp-time3">
56 1h ago
57 </span>
58 </button>
59 </div>
60 <div class="cp-divider3">
61 </div>
62 <div class="cp-group3">
63 <span class="cp-group-label3">
64 All Commands
65 </span>
66 <button class="cp-item3">
67 <span class="cp-icon3">
68 📄
69 </span>
70 <span class="cp-text3">
71 New file
72 </span>
73 <span class="cp-key3">
74 ⌘N
75 </span>
76 </button>
77 <button class="cp-item3">
78 <span class="cp-icon3">
79 💾
80 </span>
81 <span class="cp-text3">
82 Save all
83 </span>
84 <span class="cp-key3">
85 ⌘S
86 </span>
87 </button>
88 <button class="cp-item3">
89 <span class="cp-icon3">
90 🔍
91 </span>
92 <span class="cp-text3">
93 Find in files
94 </span>
95 <span class="cp-key3">
96 ⌘F
97 </span>
98 </button>
99 <button class="cp-item3">
100 <span class="cp-icon3">
101
102 </span>
103 <span class="cp-text3">
104 Preferences
105 </span>
106 <span class="cp-key3">
107 ⌘,
108 </span>
109 </button>
110 </div>
111 </div>
112 </div>
113</div>
preview
Section Tabs

Some command palettes include a tab bar to switch between sections — All, Commands, Files, People, Settings. This reduces noise when the user knows what category they're searching. Tabs use a green underline indicator for the active section.

section-tabs-palette
Live
untitled.html
HTML
1<div class="cp-demo4">
2 <div class="cp-modal4">
3 <div class="cp-input-wrap4">
4 <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
5 stroke="currentColor" stroke-width="2" stroke-linecap="round"
6 stroke-linejoin="round">
7 <circle cx="11" cy="11" r="8"/>
8 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
9 </svg>
10 <input type="text" class="cp-input4" placeholder="Search everything..." />
11 </div>
12 <div class="cp-tabs4">
13 <button class="cp-tab4 active">
14 All
15 </button>
16 <button class="cp-tab4">
17 Commands
18 </button>
19 <button class="cp-tab4">
20 Files
21 </button>
22 <button class="cp-tab4">
23 People
24 </button>
25 </div>
26 <div class="cp-results4">
27 <div class="cp-group4">
28 <span class="cp-group-label4">
29 Files
30 </span>
31 <button class="cp-item4 active">
32 <span class="cp-icon4">
33 📄
34 </span>
35 <span class="cp-text4">
36 src/components/Button.tsx
37 </span>
38 <span class="cp-badge4">
39 File
40 </span>
41 </button>
42 <button class="cp-item4">
43 <span class="cp-icon4">
44 📄
45 </span>
46 <span class="cp-text4">
47 src/styles/globals.css
48 </span>
49 <span class="cp-badge4">
50 File
51 </span>
52 </button>
53 </div>
54 <div class="cp-group4">
55 <span class="cp-group-label4">
56 People
57 </span>
58 <button class="cp-item4">
59 <span class="cp-avatar4">
60 S
61 </span>
62 <span class="cp-text4">
63 Sarah Chen
64 </span>
65 <span class="cp-badge4 green">
66 Online
67 </span>
68 </button>
69 <button class="cp-item4">
70 <span class="cp-avatar4">
71 M
72 </span>
73 <span class="cp-text4">
74 Mike Johnson
75 </span>
76 <span class="cp-badge4 yellow">
77 Away
78 </span>
79 </button>
80 </div>
81 </div>
82 </div>
83</div>
preview
Inline Spotlight Bar

An inline command bar that lives at the top of the page rather than in a modal overlay. This pattern is common in IDEs and dashboards where the palette is always visible and accessible. Focus ring highlights in green when active.

inline-command-bar
Live
untitled.html
HTML
1<div class="inline-demo">
2 <div class="inline-bar">
3 <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
4 stroke="currentColor" stroke-width="2" stroke-linecap="round"
5 stroke-linejoin="round">
6 <circle cx="11" cy="11" r="8"/>
7 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
8 </svg>
9 <input type="text" class="inline-input" placeholder="Quick actions..." />
10 <div class="inline-hints">
11 <span class="hint-key">
12 ⌘K
13 </span>
14 <span class="hint-key">
15 /
16 </span>
17 </div>
18 </div>
19</div>
preview
Keyboard Navigation

The command palette must be fully keyboard-navigable. Arrow keys navigate results, Enter selects the active item, and Escape closes the palette. Here is the key binding implementation.

command-palette-keys.js
JavaScript
1document.addEventListener('keydown', (e) => {
2 // Open palette with Cmd+K or Ctrl+K
3 if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
4 e.preventDefault();
5 togglePalette();
6 }
7
8 // Close with Escape
9 if (e.key === 'Escape') {
10 closePalette();
11 }
12
13 // Navigate results with arrow keys
14 if (!paletteOpen) return;
15 const items = document.querySelectorAll('.cp-item');
16 if (e.key === 'ArrowDown') {
17 e.preventDefault();
18 activeIndex = (activeIndex + 1) % items.length;
19 highlightItem(items, activeIndex);
20 }
21 if (e.key === 'ArrowUp') {
22 e.preventDefault();
23 activeIndex = (activeIndex - 1 + items.length) % items.length;
24 highlightItem(items, activeIndex);
25 }
26
27 // Select with Enter
28 if (e.key === 'Enter') {
29 e.preventDefault();
30 items[activeIndex]?.click();
31 closePalette();
32 }
33});

info

Always trap focus inside the command palette when it's open. Usetabindex="-1" on the container and call input.focus() on open. Arrow keys should navigate items, Enter should select, and Escape should close and restore focus to the element that triggered the palette.
🔥

pro tip

Persist recent actions in localStorage and show them before search results when the input is empty. This makes power users faster — they can repeat common actions with just⌘K → Enter without typing anything. Cap recent items at 3–5 to keep the list scannable.

warning

Don't open the command palette on every ⌘K press if the user is already in a text input. Checkdocument.activeElement.tagName to avoid conflicts with browser shortcuts or other input fields. Always provide a visual trigger button as a fallback for mouse-only users.
Best Practices
  • Use ⌘K / Ctrl+K as the universal trigger — users expect it across platforms.
  • Trap focus inside the palette and restore it to the trigger element on close.
  • Fuzzy-match search results using substring or Levenshtein distance for forgiving input.
  • Group results by category with visible labels — don't mix unrelated items.
  • Show keyboard shortcuts as badges next to each action — users learn by osmosis.
  • Cap visible results at 8–10 items per group with scroll for overflow.
  • Add a subtle backdrop blur and overlay to separate the palette from the page.
  • Debounce search input (100–150ms) if results involve API calls.
  • Use <mark> or background highlight on matched text segments for visual feedback.
  • Provide a visible trigger button (e.g., "⌘K Search...") as a mouse fallback.