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

Command Palette

CSSHTMLTailwindBootstrapUISearchKeyboardAdvanced🎯Free Tools
Introduction

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. This guide covers production-ready command palette patterns in plain HTML/CSS, Tailwind CSS, and Bootstrap — including search, grouped results, recent actions, section tabs, inline bars, empty states, keyboard navigation, and accessibility.

info

Always provide a visible trigger button as a mouse fallback. Trap focus inside the palette when it's open and restore focus to the trigger element on close.
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. Click the trigger or press ⌘K / Ctrl+K to open it.

basic-command-palette
Live
untitled.html
HTML
1<div class="cp-demo">
2 <button class="cp-trigger" id="cp-open">
3 <span class="cp-trigger-label">
4 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
5 <circle cx="11" cy="11" r="8"/>
6 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
7 </svg>
8 Search commands...
9 </span>
10 <span class="cp-shortcut">
11 ⌘K
12 </span>
13 </button>
14 <div class="cp-overlay" id="cp-overlay">
15 <div class="cp-modal" role="dialog" aria-label="Command palette" aria-modal="true">
16 <div class="cp-input-wrap">
17 <svg class="cp-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
18 <circle cx="11" cy="11" r="8"/>
19 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
20 </svg>
21 <input type="text" class="cp-input" placeholder="Type a command or search..." id="cp-input" autofocus />
22 <span class="cp-esc">
23 ESC
24 </span>
25 </div>
26 <div class="cp-results">
27 <div class="cp-group">
28 <span class="cp-group-label">
29 Suggestions
30 </span>
31 <button class="cp-item active">
32 <span class="cp-item-icon">
33 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
34 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
35 <polyline points="14 2 14 8 20 8"/>
36 <line x1="16" y1="13" x2="8" y2="13"/>
37 <line x1="16" y1="17" x2="8" y2="17"/>
38 <polyline points="10 9 9 9 8 9"/>
39 </svg>
40 </span>
41 <span class="cp-item-text">
42 Create new document
43 </span>
44 <span class="cp-item-badge">
45 ⌘N
46 </span>
47 </button>
48 <button class="cp-item">
49 <span class="cp-item-icon">
50 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
51 <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
52 </svg>
53 </span>
54 <span class="cp-item-text">
55 Open project
56 </span>
57 <span class="cp-item-badge">
58 ⌘O
59 </span>
60 </button>
61 <button class="cp-item">
62 <span class="cp-item-icon">
63 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
64 <circle cx="11" cy="11" r="8"/>
65 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
66 </svg>
67 </span>
68 <span class="cp-item-text">
69 Search files
70 </span>
71 <span class="cp-item-badge">
72 ⌘P
73 </span>
74 </button>
75 </div>
76 <div class="cp-group">
77 <span class="cp-group-label">
78 Actions
79 </span>
80 <button class="cp-item">
81 <span class="cp-item-icon">
82 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
83 <circle cx="12" cy="12" r="3"/>
84 <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
85 </svg>
86 </span>
87 <span class="cp-item-text">
88 Open settings
89 </span>
90 <span class="cp-item-badge">
91 ⌘,
92 </span>
93 </button>
94 <button class="cp-item">
95 <span class="cp-item-icon">
96 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
97 <circle cx="12" cy="12" r="5"/>
98 <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
99 </svg>
100 </span>
101 <span class="cp-item-text">
102 Toggle theme
103 </span>
104 <span class="cp-item-badge">
105 ⌘D
106 </span>
107 </button>
108 <button class="cp-item">
109 <span class="cp-item-icon">
110 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
111 <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
112 <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
113 </svg>
114 </span>
115 <span class="cp-item-text">
116 Show clipboard
117 </span>
118 <span class="cp-item-badge">
119 ⌘V
120 </span>
121 </button>
122 </div>
123 </div>
124 </div>
125 </div>
126</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="cpg-demo">
2 <div class="cpg-modal">
3 <div class="cpg-input-wrap">
4 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
5 <circle cx="11" cy="11" r="8"/>
6 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
7 </svg>
8 <input type="text" class="cpg-input" value="de" readonly />
9 <span class="cpg-count">
10 4 results
11 </span>
12 </div>
13 <div class="cpg-results">
14 <div class="cpg-group">
15 <span class="cpg-group-label">
16 Navigation
17 </span>
18 <button class="cpg-item">
19 <span class="cpg-icon" style="color:#3B82F6">
20 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
21 <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
22 </svg>
23 </span>
24 <span class="cpg-text">
25 Dashboard
26 </span>
27 <span class="cpg-desc">
28 Main overview page
29 </span>
30 </button>
31 <button class="cpg-item active">
32 <span class="cpg-icon" style="color:#3B82F6">
33 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
34 <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
35 </svg>
36 </span>
37 <span class="cpg-text">
38 <mark>
39 De
40 </mark>
41 ployments
42 </span>
43 <span class="cpg-desc">
44 View all deployments
45 </span>
46 </button>
47 </div>
48 <div class="cpg-group">
49 <span class="cpg-group-label">
50 Actions
51 </span>
52 <button class="cpg-item">
53 <span class="cpg-icon" style="color:#00FF41">
54 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
55 <path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/>
56 <path d="M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/>
57 <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/>
58 <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>
59 </svg>
60 </span>
61 <span class="cpg-text">
62 <mark>
63 De
64 </mark>
65 lete project
66 </span>
67 <span class="cpg-desc">
68 Permanently remove
69 </span>
70 </button>
71 <button class="cpg-item">
72 <span class="cpg-icon" style="color:#F97316">
73 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
74 <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
75 <line x1="12" y1="9" x2="12" y2="13"/>
76 <line x1="12" y1="17" x2="12.01" y2="17"/>
77 </svg>
78 </span>
79 <span class="cpg-text">
80 <mark>
81 De
82 </mark>
83 activate env
84 </span>
85 <span class="cpg-desc">
86 Disable environment
87 </span>
88 </button>
89 </div>
90 </div>
91 </div>
92</div>
preview
Recent Actions

Showing recently used actions at the top of an empty search helps repeat workflows faster. Recent items fade as the user types to filter. Time-since labels reinforce recency.

recent-command-palette
Live
untitled.html
HTML
1<div class="cpr-demo">
2 <div class="cpr-modal">
3 <div class="cpr-input-wrap">
4 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
5 <circle cx="11" cy="11" r="8"/>
6 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
7 </svg>
8 <input type="text" class="cpr-input" placeholder="Type a command..." />
9 <span class="cpr-esc">
10 ESC
11 </span>
12 </div>
13 <div class="cpr-results">
14 <div class="cpr-group">
15 <span class="cpr-group-label">
16 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
17 <circle cx="12" cy="12" r="10"/>
18 <polyline points="12 6 12 12 16 14"/>
19 </svg>
20 Recent
21 </span>
22 <button class="cpr-item">
23 <span class="cpr-icon">
24 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
25 <path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/>
26 <path d="M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/>
27 <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/>
28 <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>
29 </svg>
30 </span>
31 <span class="cpr-text">
32 Deploy to production
33 </span>
34 <span class="cpr-time">
35 2m ago
36 </span>
37 </button>
38 <button class="cpr-item">
39 <span class="cpr-icon">
40 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
41 <polyline points="23 4 23 10 17 10"/>
42 <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
43 </svg>
44 </span>
45 <span class="cpr-text">
46 Restart dev server
47 </span>
48 <span class="cpr-time">
49 15m ago
50 </span>
51 </button>
52 <button class="cpr-item">
53 <span class="cpr-icon">
54 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
55 <line x1="16.5" y1="9.4" x2="7.5" y2="4.21"/>
56 <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
57 <polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
58 <line x1="12" y1="22.08" x2="12" y2="12"/>
59 </svg>
60 </span>
61 <span class="cpr-text">
62 Build for staging
63 </span>
64 <span class="cpr-time">
65 1h ago
66 </span>
67 </button>
68 </div>
69 <div class="cpr-divider">
70 </div>
71 <div class="cpr-group">
72 <span class="cpr-group-label">
73 All Commands
74 </span>
75 <button class="cpr-item">
76 <span class="cpr-icon">
77 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
78 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
79 <polyline points="14 2 14 8 20 8"/>
80 <line x1="16" y1="13" x2="8" y2="13"/>
81 <line x1="16" y1="17" x2="8" y2="17"/>
82 <polyline points="10 9 9 9 8 9"/>
83 </svg>
84 </span>
85 <span class="cpr-text">
86 New file
87 </span>
88 <span class="cpr-key">
89 ⌘N
90 </span>
91 </button>
92 <button class="cpr-item">
93 <span class="cpr-icon">
94 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
95 <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
96 <polyline points="17 21 17 13 7 13 7 21"/>
97 <polyline points="7 3 7 8 15 8"/>
98 </svg>
99 </span>
100 <span class="cpr-text">
101 Save all
102 </span>
103 <span class="cpr-key">
104 ⌘S
105 </span>
106 </button>
107 <button class="cpr-item">
108 <span class="cpr-icon">
109 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
110 <circle cx="11" cy="11" r="8"/>
111 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
112 </svg>
113 </span>
114 <span class="cpr-text">
115 Find in files
116 </span>
117 <span class="cpr-key">
118 ⌘F
119 </span>
120 </button>
121 <button class="cpr-item">
122 <span class="cpr-icon">
123 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
124 <circle cx="12" cy="12" r="3"/>
125 <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
126 </svg>
127 </span>
128 <span class="cpr-text">
129 Preferences
130 </span>
131 <span class="cpr-key">
132 ⌘,
133 </span>
134 </button>
135 </div>
136 </div>
137 </div>
138</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="cpt-demo">
2 <div class="cpt-modal">
3 <div class="cpt-input-wrap">
4 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
5 <circle cx="11" cy="11" r="8"/>
6 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
7 </svg>
8 <input type="text" class="cpt-input" placeholder="Search everything..." />
9 </div>
10 <div class="cpt-tabs">
11 <button class="cpt-tab active">
12 All
13 </button>
14 <button class="cpt-tab">
15 Commands
16 </button>
17 <button class="cpt-tab">
18 Files
19 </button>
20 <button class="cpt-tab">
21 People
22 </button>
23 </div>
24 <div class="cpt-results">
25 <div class="cpt-group">
26 <span class="cpt-group-label">
27 Files
28 </span>
29 <button class="cpt-item active">
30 <span class="cpt-icon">
31 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
32 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
33 <polyline points="14 2 14 8 20 8"/>
34 <line x1="16" y1="13" x2="8" y2="13"/>
35 <line x1="16" y1="17" x2="8" y2="17"/>
36 <polyline points="10 9 9 9 8 9"/>
37 </svg>
38 </span>
39 <span class="cpt-text">
40 src/components/Button.tsx
41 </span>
42 <span class="cpt-badge">
43 File
44 </span>
45 </button>
46 <button class="cpt-item">
47 <span class="cpt-icon">
48 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
49 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
50 <polyline points="14 2 14 8 20 8"/>
51 <line x1="16" y1="13" x2="8" y2="13"/>
52 <line x1="16" y1="17" x2="8" y2="17"/>
53 <polyline points="10 9 9 9 8 9"/>
54 </svg>
55 </span>
56 <span class="cpt-text">
57 src/styles/globals.css
58 </span>
59 <span class="cpt-badge">
60 File
61 </span>
62 </button>
63 </div>
64 <div class="cpt-group">
65 <span class="cpt-group-label">
66 People
67 </span>
68 <button class="cpt-item">
69 <span class="cpt-avatar">
70 S
71 </span>
72 <span class="cpt-text">
73 Sarah Chen
74 </span>
75 <span class="cpt-badge green">
76 Online
77 </span>
78 </button>
79 <button class="cpt-item">
80 <span class="cpt-avatar">
81 M
82 </span>
83 <span class="cpt-text">
84 Mike Johnson
85 </span>
86 <span class="cpt-badge yellow">
87 Away
88 </span>
89 </button>
90 </div>
91 </div>
92 </div>
93</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="cpsi-demo">
2 <div class="cpsi-bar">
3 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
4 <circle cx="11" cy="11" r="8"/>
5 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
6 </svg>
7 <input type="text" class="cpsi-input" placeholder="Quick actions..." />
8 <div class="cpsi-hints">
9 <span class="cpsi-key">
10 ⌘K
11 </span>
12 <span class="cpsi-key">
13 /
14 </span>
15 </div>
16 </div>
17</div>
preview
Empty & Error States

Command palettes need clear empty states when no results match and error states when search fails. Both should offer a recovery action and stay within the palette context.

empty-error-states
Live
untitled.html
HTML
1<div class="cpe-demo">
2 <div class="cpe-stack">
3 <div class="cpe-modal">
4 <div class="cpe-input-wrap">
5 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" 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="cpe-input" value="xyz" readonly />
10 </div>
11 <div class="cpe-empty">
12 <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
13 <circle cx="11" cy="11" r="8"/>
14 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
15 </svg>
16 <p class="cpe-title">
17 No results found
18 </p>
19 <p class="cpe-subtitle">
20 We couldn&apos;t find anything matching &quot;xyz&quot;.
21 </p>
22 <button class="cpe-action">
23 Clear search
24 </button>
25 </div>
26 </div>
27 <div class="cpe-modal">
28 <div class="cpe-input-wrap">
29 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
30 <circle cx="11" cy="11" r="8"/>
31 <line x1="21" y1="21" x2="16.65" y2="16.65"/>
32 </svg>
33 <input type="text" class="cpe-input" placeholder="Search..." />
34 </div>
35 <div class="cpe-error">
36 <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
37 <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
38 <line x1="12" y1="9" x2="12" y2="13"/>
39 <line x1="12" y1="17" x2="12.01" y2="17"/>
40 </svg>
41 <p class="cpe-title">
42 Search failed
43 </p>
44 <p class="cpe-subtitle">
45 Couldn&apos;t connect to the search service.
46 </p>
47 <button class="cpe-action error">
48 Retry
49 </button>
50 </div>
51 </div>
52 </div>
53</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});
Accessibility

Command palettes are keyboard-first UI patterns, so accessibility is not optional. The palette must announce itself, trap focus, and expose the correct ARIA states to assistive technologies.

  • Wrap the palette in a container with role="dialog" and aria-modal="true".
  • Move focus to the search input immediately when the palette opens.
  • Trap focus inside the palette while it is open; restore focus to the trigger on close.
  • Use real <button> elements for results so they are keyboard-activatable.
  • Highlight the active result visually and ensure screen readers can detect selection changes via a live region.
  • Provide an accessible label for every icon-only control using aria-label.
  • Announce result counts and empty states with an aria-live="polite" region.
  • Respect the user's prefers-reduced-motion setting by disabling entrance animations.
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.

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.