Command
A searchable command list for navigating actions with the keyboard.
@data-slot/commandSource ↗bun add @data-slot/commandnpm install @data-slot/commandpnpm add @data-slot/commandAnatomy
Item values are inferred from their text, so command-shortcut hints never affect search matches. command-empty appears when nothing ranks.
<div data-slot="command" data-label="Command Menu">
<div data-slot="command-input-wrapper">
<input data-slot="command-input" placeholder="Type a command..." />
</div>
<div data-slot="command-list">
<div data-slot="command-empty" hidden>No results.</div>
<div data-slot="command-group">
<div data-slot="command-group-heading">Actions</div>
<div data-slot="command-item">Open Project</div>
<div data-slot="command-item">Invite Teammate</div>
</div>
<div data-slot="command-separator"></div>
<div data-slot="command-item" data-value="settings">
Settings
<span data-slot="command-shortcut">⌘,</span>
</div>
</div>
</div>
Examples
The modal palette examples also need @data-slot/dialog:
npm install @data-slot/command @data-slot/dialog
Inline command list
Inline palettes work as always-open searchable lists. Active state is exposed throughdata-selected andaria-selected. Keyboard navigation is handled at the palette root, without forcing focus into the input on generic box clicks.
Show code
<label for="command-basic-command-inline" class="command-field-label">Workspace</label>
<div data-slot="command" data-label="Workspace Command Menu" class="command-root">
<div data-slot="command-input-wrapper" class="command-input-shell">
<span class="command-search-icon">⌕</span>
<input
data-slot="command-input"
id="command-basic-command-inline"
class="command-input"
placeholder="Search commands..."
/>
</div>
<div data-slot="command-list" class="command-list">
<div data-slot="command-empty" class="command-empty" hidden>No results.</div>
<div data-slot="command-group" class="command-group">
<div data-slot="command-group-heading" class="command-heading">Project</div>
<div data-slot="command-item" class="command-item">
Open Dashboard
<span data-slot="command-shortcut" class="command-shortcut">↵</span>
</div>
<div data-slot="command-item" data-keywords="invite,team" class="command-item">
Invite Teammate
<span data-slot="command-shortcut" class="command-shortcut">⌘I</span>
</div>
</div>
<div data-slot="command-separator" class="command-separator"></div>
<div data-slot="command-group" class="command-group">
<div data-slot="command-group-heading" class="command-heading">Preferences</div>
<div data-slot="command-item" data-value="settings" class="command-item">
Open Settings
<span data-slot="command-shortcut" class="command-shortcut">⌘,</span>
</div>
<div data-slot="command-item" data-disabled class="command-item command-item-disabled">
Billing Portal
<span data-slot="command-shortcut" class="command-shortcut">Soon</span>
</div>
</div>
</div>
</div>
<style>
.command-root {
width: 100%;
border: 1px solid var(--border);
background: linear-gradient(in oklab, var(--bg), var(--code-bg));
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
max-width: 28rem;
}
.command-root-dialog {
width: 100%;
max-width: none;
overflow: hidden;
box-shadow: none;
}
.command-field-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
color: var(--muted);
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1rem;
}
.command-input-shell {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.command-search-icon {
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-input {
width: 100%;
border: none;
background: transparent;
font-size: 1rem;
color: var(--text);
outline: none;
line-height: 1.5rem;
}
.command-input::placeholder {
color: var(--muted);
}
.command-list {
max-height: 18rem;
overflow: auto;
padding: 0.375rem;
}
.command-heading {
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--muted);
line-height: 1rem;
}
.command-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.75rem;
color: var(--text);
cursor: default;
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-item[data-selected] {
background: var(--code-bg);
}
.command-shortcut {
margin-left: auto;
color: var(--muted);
font-size: 0.75rem;
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1rem;
}
.command-item-disabled {
color: var(--muted);
}
.command-separator {
height: 1px;
margin: 0.25rem 0;
background: color-mix(in srgb, var(--border) 70%, transparent);
}
.command-empty {
padding: 1rem 0.75rem;
text-align: center;
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-dialog-trigger {
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.command-dialog-trigger:hover { opacity: 0.9; }
.command-dialog-overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 0.45);
opacity: 0;
transition: opacity 0.2s ease;
z-index: 50;
}
.command-dialog-overlay[data-open] {
opacity: 1;
}
.command-dialog-overlay[data-starting-style],
.command-dialog-overlay[data-ending-style] {
opacity: 0;
}
.command-dialog-panel {
position: fixed;
top: 16.666667%;
left: 50%;
transform: translateX(-50%);
width: 91.666667%;
padding: 1rem;
border: 1px solid var(--border);
background: var(--code-bg);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
opacity: 0;
transition:
opacity 0.2s ease,
transform 0.2s ease;
z-index: 60;
max-width: 36rem;
}
.command-dialog-panel[data-open] {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.command-dialog-panel[data-starting-style],
.command-dialog-panel[data-ending-style] {
opacity: 0;
transform: translateX(-50%) translateY(0.5rem);
}
.command-dialog-title {
margin: 0 0 0.25rem;
font-size: 1rem;
font-weight: 700;
color: var(--text);
line-height: 1.5rem;
}
.command-dialog-description {
margin: 0 0 1rem;
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-item-disabled .command-shortcut { color: inherit; }
</style><label for="command-basic-command-inline" class="mb-2 block text-xs font-semibold uppercase tracking-wider text-[var(--muted)]">
Workspace
</label>
<div
data-slot="command"
data-label="Workspace Command Menu"
class="w-full max-w-md border border-[var(--border)] bg-linear-to-b from-[var(--bg)] to-[var(--code-bg)] shadow-xl"
>
<div
data-slot="command-input-wrapper"
class="flex items-center gap-2 border-b border-[var(--border)]/60 px-4 py-3"
>
<span class="text-sm text-[var(--muted)]">⌕</span>
<input
data-slot="command-input"
id="command-basic-command-inline"
class="w-full border-none bg-transparent text-base text-[var(--text)] outline-none placeholder:text-[var(--muted)]"
placeholder="Search commands..."
/>
</div>
<div data-slot="command-list" class="max-h-72 overflow-auto p-1.5">
<div data-slot="command-empty" class="px-3 py-4 text-center text-sm text-[var(--muted)]" hidden>No results.</div>
<div data-slot="command-group">
<div data-slot="command-group-heading" class="px-3 py-2 text-xs font-bold uppercase tracking-wider text-[var(--muted)]">
Project
</div>
<div
data-slot="command-item"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Open Dashboard
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">↵</span>
</div>
<div
data-slot="command-item"
data-keywords="invite,team"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Invite Teammate
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">⌘I</span>
</div>
</div>
<div data-slot="command-separator" class="my-1 h-px bg-[var(--border)]/70"></div>
<div data-slot="command-group">
<div data-slot="command-group-heading" class="px-3 py-2 text-xs font-bold uppercase tracking-wider text-[var(--muted)]">
Preferences
</div>
<div
data-slot="command-item"
data-value="settings"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Open Settings
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">⌘,</span>
</div>
<div
data-slot="command-item"
data-disabled
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--muted)]"
>
Billing Portal
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">Soon</span>
</div>
</div>
</div>
</div>import { create } from "@data-slot/command";
// Initialize after the markup is in the document.
const controllers = create();
// Clean up before removing the component.
// controllers.forEach((controller) => controller.destroy());Command Palette
Search for a command to run across your workspace.
Command Palette
Search for a command to run across your workspace.
Compose @data-slot/command insidedialog-content to build modal palettes with the same strict cmdk-style focus behavior.
Show code
<div data-slot="dialog" class="command-dialog">
<button data-slot="dialog-trigger" class="command-dialog-trigger">Open Command Palette</button>
<div data-slot="dialog-overlay" class="command-dialog-overlay" hidden></div>
<div data-slot="dialog-content" class="command-dialog-panel" hidden>
<h2 data-slot="dialog-title" class="command-dialog-title">Command Palette</h2>
<p data-slot="dialog-description" class="command-dialog-description">
Search for a command to run across your workspace.
</p>
<div data-slot="command" data-label="Global Command Palette" class="command-root command-root-dialog">
<div data-slot="command-input-wrapper" class="command-input-shell">
<span class="command-search-icon">⌕</span>
<input data-slot="command-input" class="command-input" placeholder="Search commands..." />
</div>
<div data-slot="command-list" class="command-list">
<div data-slot="command-empty" class="command-empty" hidden>No results.</div>
<div data-slot="command-item" class="command-item">
New Issue
<span data-slot="command-shortcut" class="command-shortcut">⌘N</span>
</div>
<div data-slot="command-item" class="command-item">
Jump to Inbox
<span data-slot="command-shortcut" class="command-shortcut">G I</span>
</div>
<div data-slot="command-item" class="command-item">
Open Settings
<span data-slot="command-shortcut" class="command-shortcut">⌘,</span>
</div>
</div>
</div>
</div>
</div>
<style>
.command-root {
width: 100%;
border: 1px solid var(--border);
background: linear-gradient(in oklab, var(--bg), var(--code-bg));
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
max-width: 28rem;
}
.command-root-dialog {
width: 100%;
max-width: none;
overflow: hidden;
box-shadow: none;
}
.command-field-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
color: var(--muted);
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1rem;
}
.command-input-shell {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.command-search-icon {
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-input {
width: 100%;
border: none;
background: transparent;
font-size: 1rem;
color: var(--text);
outline: none;
line-height: 1.5rem;
}
.command-input::placeholder {
color: var(--muted);
}
.command-list {
max-height: 18rem;
overflow: auto;
padding: 0.375rem;
}
.command-heading {
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--muted);
line-height: 1rem;
}
.command-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.75rem;
color: var(--text);
cursor: default;
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-item[data-selected] {
background: var(--code-bg);
}
.command-shortcut {
margin-left: auto;
color: var(--muted);
font-size: 0.75rem;
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1rem;
}
.command-item-disabled {
color: var(--muted);
}
.command-separator {
height: 1px;
margin: 0.25rem 0;
background: color-mix(in srgb, var(--border) 70%, transparent);
}
.command-empty {
padding: 1rem 0.75rem;
text-align: center;
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-dialog-trigger {
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.command-dialog-trigger:hover { opacity: 0.9; }
.command-dialog-overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 0.45);
opacity: 0;
transition: opacity 0.2s ease;
z-index: 50;
}
.command-dialog-overlay[data-open] {
opacity: 1;
}
.command-dialog-overlay[data-starting-style],
.command-dialog-overlay[data-ending-style] {
opacity: 0;
}
.command-dialog-panel {
position: fixed;
top: 16.666667%;
left: 50%;
transform: translateX(-50%);
width: 91.666667%;
padding: 1rem;
border: 1px solid var(--border);
background: var(--code-bg);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
opacity: 0;
transition:
opacity 0.2s ease,
transform 0.2s ease;
z-index: 60;
max-width: 36rem;
}
.command-dialog-panel[data-open] {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.command-dialog-panel[data-starting-style],
.command-dialog-panel[data-ending-style] {
opacity: 0;
transform: translateX(-50%) translateY(0.5rem);
}
.command-dialog-title {
margin: 0 0 0.25rem;
font-size: 1rem;
font-weight: 700;
color: var(--text);
line-height: 1.5rem;
}
.command-dialog-description {
margin: 0 0 1rem;
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-item-disabled .command-shortcut { color: inherit; }
</style><div data-slot="dialog">
<button
data-slot="dialog-trigger"
class="border border-[var(--border)] bg-[var(--surface)] px-4 py-2 text-sm font-medium text-text transition hover:opacity-90"
>
Open Command Palette
</button>
<div
data-slot="dialog-overlay"
class="fixed inset-0 z-50 bg-black/45 opacity-0 transition-opacity duration-200 data-open:opacity-100 data-starting-style:opacity-0 data-ending-style:opacity-0"
hidden
></div>
<div
data-slot="dialog-content"
class="fixed left-1/2 top-1/6 z-60 w-11/12 max-w-xl -translate-x-1/2 border border-[var(--border)] bg-[var(--code-bg)] p-4 opacity-0 shadow-2xl transition-all duration-200 data-open:opacity-100 data-starting-style:translate-y-2 data-starting-style:opacity-0 data-ending-style:translate-y-2 data-ending-style:opacity-0"
hidden
>
<h2 data-slot="dialog-title" class="mb-1 text-base font-bold text-[var(--text)]">Command Palette</h2>
<p data-slot="dialog-description" class="mb-4 text-sm text-[var(--muted)]">Search for a command to run across your workspace.</p>
<div
data-slot="command"
data-label="Global Command Palette"
class="overflow-hidden border border-[var(--border)] bg-linear-to-b from-[var(--bg)] to-[var(--code-bg)]"
>
<div data-slot="command-input-wrapper" class="flex items-center gap-2 border-b border-[var(--border)]/60 px-4 py-3">
<span class="text-sm text-[var(--muted)]">⌕</span>
<input
data-slot="command-input"
class="w-full border-none bg-transparent text-base text-[var(--text)] outline-none placeholder:text-[var(--muted)]"
placeholder="Search commands..."
/>
</div>
<div data-slot="command-list" class="max-h-72 overflow-auto p-1.5">
<div data-slot="command-empty" class="px-3 py-4 text-center text-sm text-[var(--muted)]" hidden>No results.</div>
<div
data-slot="command-item"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
New Issue
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">⌘N</span>
</div>
<div
data-slot="command-item"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Jump to Inbox
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">G I</span>
</div>
<div
data-slot="command-item"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Open Settings
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">⌘,</span>
</div>
</div>
</div>
</div>
</div>import { create } from "@data-slot/command";
import { create as createDialogs } from "@data-slot/dialog";
// Initialize after the markup is in the document.
const controllers = [...create(), ...createDialogs()];
// Clean up before removing the component.
// controllers.forEach((controller) => controller.destroy());Loop keyboard navigation
With data-loop, pressing Down on the last item wraps back to the first.
Inline palettes work as always-open searchable lists. Active state is exposed throughdata-selected andaria-selected. Keyboard navigation is handled at the palette root, without forcing focus into the input on generic box clicks.
Show code
<label for="command-extra-command-inline" class="command-field-label">Workspace</label>
<div data-slot="command" data-loop data-label="Workspace Command Menu" class="command-root">
<div data-slot="command-input-wrapper" class="command-input-shell">
<span class="command-search-icon">⌕</span>
<input
data-slot="command-input"
id="command-extra-command-inline"
class="command-input"
placeholder="Search commands..."
/>
</div>
<div data-slot="command-list" class="command-list">
<div data-slot="command-empty" class="command-empty" hidden>No results.</div>
<div data-slot="command-group" class="command-group">
<div data-slot="command-group-heading" class="command-heading">Project</div>
<div data-slot="command-item" class="command-item">
Open Dashboard
<span data-slot="command-shortcut" class="command-shortcut">↵</span>
</div>
<div data-slot="command-item" data-keywords="invite,team" class="command-item">
Invite Teammate
<span data-slot="command-shortcut" class="command-shortcut">⌘I</span>
</div>
</div>
<div data-slot="command-separator" class="command-separator"></div>
<div data-slot="command-group" class="command-group">
<div data-slot="command-group-heading" class="command-heading">Preferences</div>
<div data-slot="command-item" data-value="settings" class="command-item">
Open Settings
<span data-slot="command-shortcut" class="command-shortcut">⌘,</span>
</div>
<div data-slot="command-item" data-disabled class="command-item command-item-disabled">
Billing Portal
<span data-slot="command-shortcut" class="command-shortcut">Soon</span>
</div>
</div>
</div>
</div>
<style>
.command-root {
width: 100%;
border: 1px solid var(--border);
background: linear-gradient(in oklab, var(--bg), var(--code-bg));
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
max-width: 28rem;
}
.command-root-dialog {
width: 100%;
max-width: none;
overflow: hidden;
box-shadow: none;
}
.command-field-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
color: var(--muted);
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1rem;
}
.command-input-shell {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.command-search-icon {
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-input {
width: 100%;
border: none;
background: transparent;
font-size: 1rem;
color: var(--text);
outline: none;
line-height: 1.5rem;
}
.command-input::placeholder {
color: var(--muted);
}
.command-list {
max-height: 18rem;
overflow: auto;
padding: 0.375rem;
}
.command-heading {
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--muted);
line-height: 1rem;
}
.command-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.75rem;
color: var(--text);
cursor: default;
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-item[data-selected] {
background: var(--code-bg);
}
.command-shortcut {
margin-left: auto;
color: var(--muted);
font-size: 0.75rem;
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1rem;
}
.command-item-disabled {
color: var(--muted);
}
.command-separator {
height: 1px;
margin: 0.25rem 0;
background: color-mix(in srgb, var(--border) 70%, transparent);
}
.command-empty {
padding: 1rem 0.75rem;
text-align: center;
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-dialog-trigger {
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.command-dialog-trigger:hover { opacity: 0.9; }
.command-dialog-overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 0.45);
opacity: 0;
transition: opacity 0.2s ease;
z-index: 50;
}
.command-dialog-overlay[data-open] {
opacity: 1;
}
.command-dialog-overlay[data-starting-style],
.command-dialog-overlay[data-ending-style] {
opacity: 0;
}
.command-dialog-panel {
position: fixed;
top: 16.666667%;
left: 50%;
transform: translateX(-50%);
width: 91.666667%;
padding: 1rem;
border: 1px solid var(--border);
background: var(--code-bg);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
opacity: 0;
transition:
opacity 0.2s ease,
transform 0.2s ease;
z-index: 60;
max-width: 36rem;
}
.command-dialog-panel[data-open] {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.command-dialog-panel[data-starting-style],
.command-dialog-panel[data-ending-style] {
opacity: 0;
transform: translateX(-50%) translateY(0.5rem);
}
.command-dialog-title {
margin: 0 0 0.25rem;
font-size: 1rem;
font-weight: 700;
color: var(--text);
line-height: 1.5rem;
}
.command-dialog-description {
margin: 0 0 1rem;
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-item-disabled .command-shortcut { color: inherit; }
</style><label for="command-extra-command-inline" class="mb-2 block text-xs font-semibold uppercase tracking-wider text-[var(--muted)]">
Workspace
</label>
<div
data-slot="command" data-loop
data-label="Workspace Command Menu"
class="w-full max-w-md border border-[var(--border)] bg-linear-to-b from-[var(--bg)] to-[var(--code-bg)] shadow-xl"
>
<div
data-slot="command-input-wrapper"
class="flex items-center gap-2 border-b border-[var(--border)]/60 px-4 py-3"
>
<span class="text-sm text-[var(--muted)]">⌕</span>
<input
data-slot="command-input"
id="command-extra-command-inline"
class="w-full border-none bg-transparent text-base text-[var(--text)] outline-none placeholder:text-[var(--muted)]"
placeholder="Search commands..."
/>
</div>
<div data-slot="command-list" class="max-h-72 overflow-auto p-1.5">
<div data-slot="command-empty" class="px-3 py-4 text-center text-sm text-[var(--muted)]" hidden>No results.</div>
<div data-slot="command-group">
<div data-slot="command-group-heading" class="px-3 py-2 text-xs font-bold uppercase tracking-wider text-[var(--muted)]">
Project
</div>
<div
data-slot="command-item"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Open Dashboard
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">↵</span>
</div>
<div
data-slot="command-item"
data-keywords="invite,team"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Invite Teammate
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">⌘I</span>
</div>
</div>
<div data-slot="command-separator" class="my-1 h-px bg-[var(--border)]/70"></div>
<div data-slot="command-group">
<div data-slot="command-group-heading" class="px-3 py-2 text-xs font-bold uppercase tracking-wider text-[var(--muted)]">
Preferences
</div>
<div
data-slot="command-item"
data-value="settings"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Open Settings
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">⌘,</span>
</div>
<div
data-slot="command-item"
data-disabled
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--muted)]"
>
Billing Portal
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">Soon</span>
</div>
</div>
</div>
</div>import { create } from "@data-slot/command";
// Initialize after the markup is in the document.
const controllers = create();
// Clean up before removing the component.
// controllers.forEach((controller) => controller.destroy());Command Palette
Search for a command to run across your workspace.
Command Palette
Search for a command to run across your workspace.
Compose @data-slot/command insidedialog-content to build modal palettes with the same strict cmdk-style focus behavior.
Show code
<div data-slot="dialog" class="command-dialog">
<button data-slot="dialog-trigger" class="command-dialog-trigger">Open Command Palette</button>
<div data-slot="dialog-overlay" class="command-dialog-overlay" hidden></div>
<div data-slot="dialog-content" class="command-dialog-panel" hidden>
<h2 data-slot="dialog-title" class="command-dialog-title">Command Palette</h2>
<p data-slot="dialog-description" class="command-dialog-description">
Search for a command to run across your workspace.
</p>
<div data-slot="command" data-loop data-label="Global Command Palette" class="command-root command-root-dialog">
<div data-slot="command-input-wrapper" class="command-input-shell">
<span class="command-search-icon">⌕</span>
<input data-slot="command-input" class="command-input" placeholder="Search commands..." />
</div>
<div data-slot="command-list" class="command-list">
<div data-slot="command-empty" class="command-empty" hidden>No results.</div>
<div data-slot="command-item" class="command-item">
New Issue
<span data-slot="command-shortcut" class="command-shortcut">⌘N</span>
</div>
<div data-slot="command-item" class="command-item">
Jump to Inbox
<span data-slot="command-shortcut" class="command-shortcut">G I</span>
</div>
<div data-slot="command-item" class="command-item">
Open Settings
<span data-slot="command-shortcut" class="command-shortcut">⌘,</span>
</div>
</div>
</div>
</div>
</div>
<style>
.command-root {
width: 100%;
border: 1px solid var(--border);
background: linear-gradient(in oklab, var(--bg), var(--code-bg));
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
max-width: 28rem;
}
.command-root-dialog {
width: 100%;
max-width: none;
overflow: hidden;
box-shadow: none;
}
.command-field-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.75rem;
font-weight: 600;
color: var(--muted);
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1rem;
}
.command-input-shell {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.command-search-icon {
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-input {
width: 100%;
border: none;
background: transparent;
font-size: 1rem;
color: var(--text);
outline: none;
line-height: 1.5rem;
}
.command-input::placeholder {
color: var(--muted);
}
.command-list {
max-height: 18rem;
overflow: auto;
padding: 0.375rem;
}
.command-heading {
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--muted);
line-height: 1rem;
}
.command-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.75rem;
color: var(--text);
cursor: default;
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-item[data-selected] {
background: var(--code-bg);
}
.command-shortcut {
margin-left: auto;
color: var(--muted);
font-size: 0.75rem;
letter-spacing: 0.05em;
text-transform: uppercase;
line-height: 1rem;
}
.command-item-disabled {
color: var(--muted);
}
.command-separator {
height: 1px;
margin: 0.25rem 0;
background: color-mix(in srgb, var(--border) 70%, transparent);
}
.command-empty {
padding: 1rem 0.75rem;
text-align: center;
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-dialog-trigger {
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.command-dialog-trigger:hover { opacity: 0.9; }
.command-dialog-overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 0.45);
opacity: 0;
transition: opacity 0.2s ease;
z-index: 50;
}
.command-dialog-overlay[data-open] {
opacity: 1;
}
.command-dialog-overlay[data-starting-style],
.command-dialog-overlay[data-ending-style] {
opacity: 0;
}
.command-dialog-panel {
position: fixed;
top: 16.666667%;
left: 50%;
transform: translateX(-50%);
width: 91.666667%;
padding: 1rem;
border: 1px solid var(--border);
background: var(--code-bg);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
opacity: 0;
transition:
opacity 0.2s ease,
transform 0.2s ease;
z-index: 60;
max-width: 36rem;
}
.command-dialog-panel[data-open] {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.command-dialog-panel[data-starting-style],
.command-dialog-panel[data-ending-style] {
opacity: 0;
transform: translateX(-50%) translateY(0.5rem);
}
.command-dialog-title {
margin: 0 0 0.25rem;
font-size: 1rem;
font-weight: 700;
color: var(--text);
line-height: 1.5rem;
}
.command-dialog-description {
margin: 0 0 1rem;
color: var(--muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.command-item-disabled .command-shortcut { color: inherit; }
</style><div data-slot="dialog">
<button
data-slot="dialog-trigger"
class="border border-[var(--border)] bg-[var(--surface)] px-4 py-2 text-sm font-medium text-text transition hover:opacity-90"
>
Open Command Palette
</button>
<div
data-slot="dialog-overlay"
class="fixed inset-0 z-50 bg-black/45 opacity-0 transition-opacity duration-200 data-open:opacity-100 data-starting-style:opacity-0 data-ending-style:opacity-0"
hidden
></div>
<div
data-slot="dialog-content"
class="fixed left-1/2 top-1/6 z-60 w-11/12 max-w-xl -translate-x-1/2 border border-[var(--border)] bg-[var(--code-bg)] p-4 opacity-0 shadow-2xl transition-all duration-200 data-open:opacity-100 data-starting-style:translate-y-2 data-starting-style:opacity-0 data-ending-style:translate-y-2 data-ending-style:opacity-0"
hidden
>
<h2 data-slot="dialog-title" class="mb-1 text-base font-bold text-[var(--text)]">Command Palette</h2>
<p data-slot="dialog-description" class="mb-4 text-sm text-[var(--muted)]">Search for a command to run across your workspace.</p>
<div
data-slot="command" data-loop
data-label="Global Command Palette"
class="overflow-hidden border border-[var(--border)] bg-linear-to-b from-[var(--bg)] to-[var(--code-bg)]"
>
<div data-slot="command-input-wrapper" class="flex items-center gap-2 border-b border-[var(--border)]/60 px-4 py-3">
<span class="text-sm text-[var(--muted)]">⌕</span>
<input
data-slot="command-input"
class="w-full border-none bg-transparent text-base text-[var(--text)] outline-none placeholder:text-[var(--muted)]"
placeholder="Search commands..."
/>
</div>
<div data-slot="command-list" class="max-h-72 overflow-auto p-1.5">
<div data-slot="command-empty" class="px-3 py-4 text-center text-sm text-[var(--muted)]" hidden>No results.</div>
<div
data-slot="command-item"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
New Issue
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">⌘N</span>
</div>
<div
data-slot="command-item"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Jump to Inbox
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">G I</span>
</div>
<div
data-slot="command-item"
class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-[var(--text)] data-selected:bg-[var(--code-bg)]"
>
Open Settings
<span data-slot="command-shortcut" class="ml-auto text-xs uppercase tracking-wider text-[var(--muted)]">⌘,</span>
</div>
</div>
</div>
</div>
</div>import { create } from "@data-slot/command";
import { create as createDialogs } from "@data-slot/dialog";
// Initialize after the markup is in the document.
const controllers = [...create(), ...createDialogs()];
// Clean up before removing the component.
// controllers.forEach((controller) => controller.destroy());API reference
Initialization
create(scope?)
Auto-discover and bind all command palettes in a scope (defaults to document).
import { create } from "@data-slot/command";
const controllers = create();
createCommand(root, options?)
Create a controller for a specific element.
import { createCommand } from "@data-slot/command";
const command = createCommand(element, {
defaultSearch: "set",
onSelect: (value) => console.log("Selected:", value),
});
Slots
Runtime Slots
command- Root container.command-input- Required search input that filters and ranks the commands.command-list- Required listbox container for items and groups.command-empty- Empty state shown when there are no ranked matches.command-group- Group of related items.command-group-heading- Optional label for a group.command-item- Selectable command item.command-shortcut- Optional shortcut hint inside an item; excluded when the runtime infers the item value from text. Displaying it does not register a keyboard shortcut.command-separator- Divider between sections; hidden during search unlessdata-always-renderis enabled.
Style-only Slots
command-input-wrapper- Optional wrapper around the input for styling.
Dialog Composition
Use @data-slot/dialog when you want modal presentation:
<div data-slot="dialog">
<button data-slot="dialog-trigger">Open Command Palette</button>
<div data-slot="dialog-overlay" hidden></div>
<div data-slot="dialog-content" hidden>
<h2 data-slot="dialog-title">Command Palette</h2>
<p data-slot="dialog-description">Search for a command to run.</p>
<div data-slot="command" data-label="Global Command Palette">
<input data-slot="command-input" placeholder="Search commands..." />
<div data-slot="command-list">
<div data-slot="command-empty" hidden>No results.</div>
<div data-slot="command-item">New Issue</div>
<div data-slot="command-item">Open Settings</div>
</div>
</div>
</div>
</div>
<script type="module">
import { createDialog } from "@data-slot/dialog";
import { createCommand } from "@data-slot/command";
document.querySelectorAll('[data-slot="dialog"]').forEach((el) => createDialog(el));
document.querySelectorAll('[data-slot="command"]').forEach((el) => createCommand(el));
</script>
Data Attributes
| Attribute | Applies To | Description |
|---|---|---|
data-value |
command-item, command-group |
Explicit value. If omitted on an item, value is inferred from data-label or text content |
data-label |
command-item |
Alternate text source for value inference |
data-keywords |
command-item |
Comma-separated aliases used during filtering |
data-disabled / disabled |
command-item |
Prevents navigation and selection |
data-force-mount |
command-item, command-group |
Keeps the node rendered during filtering |
data-always-render |
command-separator |
Keeps the separator visible while searching |
command-shortcut text is ignored when inferring an item value, so shadcn-style shortcut hints do not affect search matches.
Options
Options can be passed via JavaScript or data attributes on the root element. JavaScript options take precedence.
| Option | Data Attribute | Type | Default | Description |
|---|---|---|---|---|
label |
data-label |
string |
"Command Menu" |
Accessible label announced for the search input |
defaultValue |
data-default-value |
string |
First enabled visible item, or null |
Initial active item value; a nonempty defaultSearch selects the first enabled result instead |
defaultSearch |
data-default-search |
string |
"" |
Initial search text |
shouldFilter |
data-should-filter |
boolean |
true |
Enable built-in filtering and sorting; set false to manage results yourself |
loop |
data-loop |
boolean |
false |
Wrap arrow-key navigation |
disablePointerSelection |
data-disable-pointer-selection |
boolean |
false |
Disable hover-driven selection |
vimBindings |
data-vim-bindings |
boolean |
true |
Enable Ctrl+J/K/N/P shortcuts |
filter |
- | (value, search, keywords?) => number |
commandScore |
Custom ranking function |
onValueChange |
- | (value: string | null) => void |
- | Called when the active item changes |
onSearchChange |
- | (search: string) => void |
- | Called when the search query changes |
onSelect |
- | (value: string) => void |
- | Called on click or Enter selection |
Controller
interface CommandController {
readonly value: string | null;
readonly search: string;
select(value: string | null): void;
setSearch(search: string): void;
destroy(): void;
}
Events
Outbound Events
root.addEventListener("command:change", (event) => {
console.log("Active item:", event.detail.value);
});
root.addEventListener("command:search-change", (event) => {
console.log("Search:", event.detail.search);
});
root.addEventListener("command:select", (event) => {
console.log("Selected:", event.detail.value);
});
Inbound Events
root.dispatchEvent(
new CustomEvent("command:set", {
detail: {
search: "set",
value: "settings",
},
})
);
Styling
[data-slot="command-item"][data-selected] {
background: #f1f1f1;
}
[data-slot="command-item"][aria-disabled="true"] {
opacity: 0.5;
pointer-events: none;
}
[data-slot="command-list"] {
height: var(--command-list-height);
transition: height 150ms ease;
}
Keyboard Navigation
| Key | Action |
|---|---|
ArrowDown / ArrowUp |
Move to the next or previous enabled item |
Home / End |
Jump to the first or last enabled item |
Alt+ArrowDown / Alt+ArrowUp |
Jump between groups |
Ctrl+J / Ctrl+N |
Next item |
Ctrl+K / Ctrl+P |
Previous item |
Enter |
Trigger command:select for the active item |
Arrow navigation is handled on the command root like cmdk. Selecting from the input or a focused command root keeps keyboard flow intact, but clicking non-interactive palette chrome does not auto-focus the input.