ComponentsCommandPalette
CommandPalette
Let users search for actions and pages from one place.
On this page
Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.
View registry filesImport the component into your page or component file.
import {
CommandPalette,
CommandPaletteContent,
CommandPaletteDialog,
CommandPaletteTrigger,
type CommandPaletteCommand,
} from "@dethink/components";CommandPalette can render directly from typed data or be composed from anatomy primitives when a row needs app-owned markup.
Basic
Grouped action and link commands with shortcuts, disabled reasons, and destructive state.
Search actions, links, and guarded destructive commands.
Show sourceexamples/command-palette/basic.tsx
"use client";
import {
CommandPalette,
type CommandPaletteCommand,
} from "@dethink/components";
import {
Archive,
ExternalLink,
Settings2,
Trash2,
UserPlus,
} from "lucide-react";
const commands: CommandPaletteCommand[] = [
{
description: "Send an invite to this workspace",
group: "Actions",
icon: <UserPlus aria-hidden="true" className="size-4" />,
key: "invite",
keywords: ["people", "member"],
label: "Invite teammate",
shortcut: "G I",
},
{
description: "Move the current project to the archive",
group: "Actions",
icon: <Archive aria-hidden="true" className="size-4" />,
key: "archive-project",
label: "Archive project",
},
{
group: "Navigation",
href: "#settings",
icon: <Settings2 aria-hidden="true" className="size-4" />,
key: "settings",
label: "Open settings",
shortcut: "G S",
type: "link",
},
{
group: "Navigation",
href: "https://dethink.dev",
icon: <ExternalLink aria-hidden="true" className="size-4" />,
key: "docs",
label: "Open docs",
target: "_blank",
type: "link",
},
{
group: "Danger",
icon: <Trash2 aria-hidden="true" className="size-4" />,
key: "delete-workspace",
label: "Delete workspace",
destructive: true,
disabled: true,
disabledReason: "Owner access required",
},
];
export function CommandPaletteBasic() {
return (
<CommandPalette
label="Workspace commands"
description="Search actions, links, and guarded destructive commands."
commands={commands}
placeholder="Type a command..."
/>
);
}Production-shaped recipes for app shells, dashboards, and AI-native command workflows.
Global launcher
Dialog mode owns focus containment, restoration, close-on-run behavior, and the command surface.
Show sourceexamples/command-palette/global-launcher.tsx
"use client";
import { useState } from "react";
import {
CommandPalette,
CommandPaletteContent,
CommandPaletteDialog,
CommandPaletteTrigger,
type CommandPaletteCommand,
} from "@dethink/components";
import { FolderPlus, PanelTopOpen, Search, Settings2 } from "lucide-react";
export function CommandPaletteGlobalLauncher() {
const [lastRun, setLastRun] = useState("No command run yet.");
const commands: CommandPaletteCommand[] = [
{
action: () => setLastRun("Created a project draft."),
group: "Create",
icon: <FolderPlus aria-hidden="true" className="size-4" />,
key: "create-project",
label: "Create project",
shortcut: "⌘N",
},
{
action: () => setLastRun("Opened the command center."),
group: "Navigate",
icon: <PanelTopOpen aria-hidden="true" className="size-4" />,
key: "command-center",
label: "Open command center",
shortcut: "G C",
},
{
action: () => setLastRun("Opened workspace settings."),
group: "Navigate",
icon: <Settings2 aria-hidden="true" className="size-4" />,
key: "settings",
label: "Open settings",
shortcut: "G S",
},
];
return (
<div className="grid gap-4">
<CommandPaletteDialog closeOnRun motionPreset="standard">
<CommandPaletteTrigger variant="outline">
<Search aria-hidden="true" className="size-4" />
Open launcher
</CommandPaletteTrigger>
<CommandPaletteContent
title="Command menu"
description="Run workspace commands from anywhere."
>
<CommandPalette
label="Global command menu"
commands={commands}
placeholder="Search commands..."
/>
</CommandPaletteContent>
</CommandPaletteDialog>
<output className="border-border bg-muted/40 text-muted-foreground rounded-md border px-3 py-2 text-sm">
{lastRun}
</output>
</div>
);
}Sidebar inline search
A scoped command palette embedded in Sidebar navigation without turning navigation into a form control.
Dashboard action runner
Contextual commands run against the active dashboard and report execution through app state.
Run contextual commands against the active dashboard.
Show sourceexamples/command-palette/dashboard-actions.tsx
"use client";
import { useState } from "react";
import {
CommandPalette,
type CommandPaletteCommand,
} from "@dethink/components";
import { Bell, Download, RefreshCw, ShieldAlert } from "lucide-react";
export function CommandPaletteDashboardActions() {
const [status, setStatus] = useState("Waiting for an action.");
const commands: CommandPaletteCommand[] = [
{
action: () => setStatus("Queued a dashboard refresh."),
description: "Refresh all visible KPI cards",
group: "Dashboard",
icon: <RefreshCw aria-hidden="true" className="size-4" />,
key: "refresh",
label: "Refresh metrics",
shortcut: "R",
},
{
action: () => setStatus("Export started."),
description: "Download the current dashboard as CSV",
group: "Dashboard",
icon: <Download aria-hidden="true" className="size-4" />,
key: "export",
label: "Export report",
shortcut: "E",
},
{
action: () => setStatus("Notification rule opened."),
description: "Create a threshold alert",
group: "Automation",
icon: <Bell aria-hidden="true" className="size-4" />,
key: "alert",
label: "Create alert",
},
{
destructive: true,
disabled: true,
disabledReason: "Requires incident commander role",
group: "Incident",
icon: <ShieldAlert aria-hidden="true" className="size-4" />,
key: "freeze",
label: "Freeze deploys",
},
];
return (
<div className="grid gap-4 lg:grid-cols-[1fr_16rem]">
<CommandPalette
label="Dashboard actions"
description="Run contextual commands against the active dashboard."
commands={commands}
onCommandRun={(command) => {
if (!command.action) {
setStatus(`Ran ${String(command.textValue ?? command.key)}.`);
}
}}
/>
<div className="border-border bg-muted/40 rounded-md border p-4">
<div className="text-foreground text-sm font-semibold">Action log</div>
<output className="text-muted-foreground mt-2 block text-sm leading-6">
{status}
</output>
</div>
</div>
);
}Project and resource switcher
Nested pages, recents, and suggestions create a compact resource browser with Back and Escape handling.
No resource selected.
Show sourceexamples/command-palette/project-switcher.tsx
"use client";
import { useState } from "react";
import {
CommandPalette,
type CommandPaletteCommand,
} from "@dethink/components";
import { Boxes, FolderGit2, Layers3, Search } from "lucide-react";
const rootCommands: CommandPaletteCommand[] = [
{
group: "Browse",
icon: <FolderGit2 aria-hidden="true" className="size-4" />,
key: "projects",
label: "Projects",
page: "projects",
shortcut: "G P",
type: "page",
},
{
group: "Browse",
icon: <Boxes aria-hidden="true" className="size-4" />,
key: "resources",
label: "Resources",
page: "resources",
shortcut: "G R",
type: "page",
},
{
group: "Search",
icon: <Search aria-hidden="true" className="size-4" />,
key: "global-search",
label: "Search all records",
shortcut: "⌘K",
},
];
export function CommandPaletteProjectSwitcher() {
const [lastSelection, setLastSelection] = useState("No resource selected.");
return (
<div className="grid gap-4">
<CommandPalette
label="Switch workspace context"
description={lastSelection}
motionPreset="expressive"
commands={rootCommands}
recentCommands={[
{
description: "Recently opened project",
key: "recent-alpha",
label: "Alpha rollout",
action: () => setLastSelection("Opened Alpha rollout."),
},
]}
suggestedCommands={[
{
description: "Suggested resource",
key: "suggested-runbook",
label: "Payments runbook",
action: () => setLastSelection("Opened Payments runbook."),
},
]}
pages={[
{
commands: [
{
action: () => setLastSelection("Opened Alpha rollout."),
icon: <Layers3 aria-hidden="true" className="size-4" />,
key: "alpha",
label: "Alpha rollout",
},
{
action: () => setLastSelection("Opened Beta migration."),
icon: <Layers3 aria-hidden="true" className="size-4" />,
key: "beta",
label: "Beta migration",
},
],
description: "Choose an active project.",
id: "projects",
title: "Projects",
},
{
commands: [
{
action: () => setLastSelection("Opened API gateway."),
key: "api-gateway",
label: "API gateway",
},
{
action: () => setLastSelection("Opened billing ledger."),
key: "billing-ledger",
label: "Billing ledger",
},
],
description: "Jump to operational resources.",
id: "resources",
title: "Resources",
},
]}
/>
</div>
);
}NavigationMenu handoff
NavigationMenu keeps persistent links visible while CommandPalette handles keyboard command discovery.
AI command menu
Manual filtering with an app-owned async result window, loading, empty, error, retry, and live status announcements.
Ready for an AI command.
Try runbook, release, or fail.
Developer notes for the choices that make CommandPalette different from shadcn's command primitive.
Schema and execution
Use CommandPaletteCommand for action, link, page, and separator records. onCommandRun receives source, query, page stack, and close helpers.
Filtering and async
Keep built-in filtering for local commands, or set shouldFilter=false when a server owns the result window. Loading, stale, error, empty, and retry states are first-class props.
Recents and suggestions
recentCommands, suggestedCommands, and asyncCommands merge into predictable source groups with data-source hooks for styling and analytics.
Nested pages
Page commands push typed page definitions onto a stack. Back buttons, Escape handling, page announcements, and controlled pageStack are built in.
Accessibility
Use label or aria-label. Dialog mode restores focus, visible statuses use role=status, and the announcer reports selection, execution, result counts, and page changes.
Theming and motion
All surfaces use provider tokens. motionPreset controls selected-row, result, and page choreography; reducedMotion can force static rendering.
App-shell recipes
Global launchers, Sidebar inline search, NavigationMenu handoff, dashboard action runners, project switchers, and AI menus should stay command workflows rather than form controls.
Migration boundaries
Unlike a shadcn Command copy, this component does not expose cmdk internals or become a Combobox replacement. Keep form selection in Combobox, Select, AsyncSelect, or MultiSelect.
The root data API and the dialog composition API are both exported. Import anatomy primitives when you need custom rows or page frames.
| Prop | What it does | Default |
|---|---|---|
commandsCommandPaletteCommand[] | Typed action, link, page, and separator items. Commands support labels, descriptions, icons, groups, shortcuts, aliases, keywords, disabled reasons, destructive state, and metadata. | [] |
query / onQueryChangestring / (query) => void | Controls the search text for manual filtering, server search, analytics, or app-owned query persistence. | uncontrolled |
filter / sort / limit / shouldFilterfunction / function / number / boolean | Customize local ranking, order, result caps, or bypass internal filtering when the app already owns the result window. | built-in / none / none / true |
recentCommands / suggestedCommands / asyncCommandsCommandPaletteCommand[] | Additional source windows merged before base commands with stable data-source attributes and default Recent, Suggested, and Results groups. | [] |
loading / error / onRetryboolean / ReactNode / () => void | Async feedback states for remote search, including stale result messaging and accessible retry controls. | false / undefined / undefined |
pages / pageStack / onPageStackChangeCommandPalettePageDefinition[] / string[] / (stack, context) => void | Nested command pages for project switchers, resource browsers, and multistep command flows with Back handling and Escape navigation. | [] / uncontrolled / undefined |
onCommandRun / closeOnRun(command, context) => void / boolean | Central execution hook with source, query, page, stack, and close helpers. Individual commands can override close behavior. | undefined / dialog default |
motionPreset / reducedMotion"none" | "subtle" | "standard" | "expressive" / boolean | Controls result, selected-row, and page-stack choreography while preserving reduced-motion behavior. | "standard" / prefers-reduced-motion |
CommandPaletteDialogDialog composition | Wraps Dialog primitives for global launchers, focus containment, restoration, and app-shell command surfaces. | closed |