Shadcn Command
Free shadcn/ui command component for React — the cmdk-powered command menu and ⌘K palette. Nested pages, custom filtering, async search, and a Popover combobox. The examples official shadcn never shipped.
Installation
{
"registries": {
"@designrevision": "https://registry.designrevision.com/r/{name}.json"
}
}
Add to your existing components.json. Requires shadcn/ui v2.3+.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
function Command({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
className
)}
{...props}
/>
)
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
className,
showCloseButton = true,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string
description?: string
className?: string
showCloseButton?: boolean
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent
className={cn("overflow-hidden p-0", className)}
showCloseButton={showCloseButton}
>
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
function CommandInput({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div
data-slot="command-input-wrapper"
className="flex h-9 items-center gap-2 border-b px-3"
>
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
)
}
function CommandList({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
className
)}
{...props}
/>
)
}
function CommandEmpty({
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className="py-6 text-center text-sm"
{...props}
/>
)
}
function CommandGroup({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
className
)}
{...props}
/>
)
}
function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("bg-border -mx-1 h-px", className)}
{...props}
/>
)
}
function CommandItem({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function CommandShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandSeparator,
CommandShortcut,
}
Examples
Command menu
An inline menu — grouped items with icons and shortcuts, a separator, a disabled item, and a no-results state.
Packages
Props
No props documented yet.
import {
CalculatorIcon,
CalendarIcon,
CreditCardIcon,
SettingsIcon,
SmileIcon,
UserIcon,
} from "lucide-react"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command"
export default function CommandDemo01() {
return (
<Command className="rounded-lg border shadow-md md:min-w-[450px]">
<CommandInput placeholder="Type a command or search…" />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>
<CalendarIcon />
<span>Calendar</span>
</CommandItem>
<CommandItem>
<SmileIcon />
<span>Search Emoji</span>
</CommandItem>
<CommandItem disabled>
<CalculatorIcon />
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>
<UserIcon />
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem>
<CreditCardIcon />
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem>
<SettingsIcon />
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
)
}
⌘K command palette
The keyboard-driven palette — `CommandDialog` opened with a **⌘K / Ctrl+K** keydown listener, with a [Kbd](/components/kbd) hint in the trigger.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import {
CalendarIcon,
CreditCardIcon,
SettingsIcon,
SmileIcon,
UserIcon,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command"
import { Kbd } from "@/components/ui/kbd"
export default function CommandDialog01() {
const [open, setOpen] = React.useState(false)
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setOpen((open) => !open)
}
}
document.addEventListener("keydown", down)
return () => document.removeEventListener("keydown", down)
}, [])
const runCommand = React.useCallback(() => setOpen(false), [])
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
<span className="text-muted-foreground">Search…</span>
<Kbd className="ml-2">⌘K</Kbd>
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search…" />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem onSelect={runCommand}>
<CalendarIcon />
<span>Calendar</span>
</CommandItem>
<CommandItem onSelect={runCommand}>
<SmileIcon />
<span>Search Emoji</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem onSelect={runCommand}>
<UserIcon />
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem onSelect={runCommand}>
<CreditCardIcon />
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem onSelect={runCommand}>
<SettingsIcon />
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</>
)
}
Nested pages
Items push **sub-pages** onto a stack; Backspace on an empty search pops back — the cmdk multi-level pattern.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
export default function CommandNested01() {
const [pages, setPages] = React.useState<string[]>([])
const [search, setSearch] = React.useState("")
const page = pages[pages.length - 1]
const push = (next: string) => {
setPages((prev) => [...prev, next])
setSearch("")
}
return (
<Command
className="rounded-lg border shadow-md md:min-w-[450px]"
onKeyDown={(e) => {
if (pages.length > 0 && (e.key === "Escape" || (e.key === "Backspace" && !search))) {
e.preventDefault()
setPages((prev) => prev.slice(0, -1))
}
}}
>
<CommandInput
value={search}
onValueChange={setSearch}
placeholder={page ? `Search ${page}…` : "Type a command — Backspace to go back…"}
/>
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{!page && (
<CommandGroup heading="Home">
<CommandItem onSelect={() => push("projects")}>Search projects…</CommandItem>
<CommandItem onSelect={() => push("teams")}>Join a team…</CommandItem>
<CommandItem onSelect={() => push("theme")}>Change theme…</CommandItem>
</CommandGroup>
)}
{page === "projects" && (
<CommandGroup heading="Projects">
<CommandItem>Acme Marketing Site</CommandItem>
<CommandItem>Internal Dashboard</CommandItem>
<CommandItem>Mobile App</CommandItem>
</CommandGroup>
)}
{page === "teams" && (
<CommandGroup heading="Teams">
<CommandItem>Engineering</CommandItem>
<CommandItem>Design</CommandItem>
<CommandItem>Marketing</CommandItem>
</CommandGroup>
)}
{page === "theme" && (
<CommandGroup heading="Theme">
<CommandItem>Light</CommandItem>
<CommandItem>Dark</CommandItem>
<CommandItem>System</CommandItem>
</CommandGroup>
)}
</CommandList>
</Command>
)
}
Custom filter & keywords
Match items by aliases with a custom `filter` prop and per-item `keywords` — searching "appearance" finds "Toggle theme".
Packages
Props
No props documented yet.
"use client"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
export default function CommandFilter01() {
return (
<Command
className="rounded-lg border shadow-md md:min-w-[450px]"
filter={(value, search, keywords) => {
const haystack = `${value} ${(keywords ?? []).join(" ")}`.toLowerCase()
return haystack.includes(search.toLowerCase()) ? 1 : 0
}}
>
<CommandInput placeholder='Try "appearance" or "logout"…' />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Commands">
<CommandItem
value="Toggle theme"
keywords={["appearance", "dark", "light", "mode"]}
>
Toggle theme
</CommandItem>
<CommandItem value="Sign out" keywords={["logout", "exit", "leave"]}>
Sign out
</CommandItem>
<CommandItem
value="Open settings"
keywords={["preferences", "config", "options"]}
>
Open settings
</CommandItem>
<CommandItem
value="Invite member"
keywords={["add", "teammate", "user"]}
>
Invite member
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
)
}
Async / server search
Server-side search with `shouldFilter={false}`, a debounced request, and a loading state — you own the results.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
const FRAMEWORKS = [
"Next.js",
"React",
"Vue",
"Svelte",
"Angular",
"Solid",
"Astro",
"Remix",
"Nuxt",
"Qwik",
]
export default function CommandAsync01() {
const [query, setQuery] = React.useState("")
const [results, setResults] = React.useState<string[]>([])
const [loading, setLoading] = React.useState(false)
React.useEffect(() => {
if (!query) {
setResults([])
setLoading(false)
return
}
// Simulate a debounced server request.
setLoading(true)
const id = setTimeout(() => {
setResults(
FRAMEWORKS.filter((f) => f.toLowerCase().includes(query.toLowerCase()))
)
setLoading(false)
}, 500)
return () => clearTimeout(id)
}, [query])
return (
<Command
shouldFilter={false}
className="rounded-lg border shadow-md md:min-w-[450px]"
>
<CommandInput
value={query}
onValueChange={setQuery}
placeholder="Search frameworks (simulated fetch)…"
/>
<CommandList>
{loading && (
<div className="py-6 text-center text-sm text-muted-foreground">
Searching…
</div>
)}
{!loading && query && results.length === 0 && (
<CommandEmpty>No results found.</CommandEmpty>
)}
{!loading && results.length > 0 && (
<CommandGroup heading="Frameworks">
{results.map((framework) => (
<CommandItem key={framework} value={framework}>
{framework}
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</Command>
)
}
Combobox (Popover + Command)
The other major cmdk usage — a searchable single-select combobox built from Command inside a [Popover](/components/popover).
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
const frameworks = [
{ value: "next.js", label: "Next.js" },
{ value: "sveltekit", label: "SvelteKit" },
{ value: "nuxt.js", label: "Nuxt.js" },
{ value: "remix", label: "Remix" },
{ value: "astro", label: "Astro" },
]
export default function CommandCombobox01() {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState("")
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-[200px] justify-between"
>
{value
? frameworks.find((f) => f.value === value)?.label
: "Select framework…"}
<ChevronsUpDownIcon className="opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search framework…" />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
value={framework.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
setOpen(false)
}}
>
<CheckIcon
className={cn(
"mr-2",
value === framework.value ? "opacity-100" : "opacity-0"
)}
/>
{framework.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
Disabled & empty
Dimmed, unselectable items via the `disabled` prop, plus the `CommandEmpty` no-results message.
Packages
Props
No props documented yet.
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
} from "@/components/ui/command"
export default function CommandDisabled01() {
return (
<Command className="rounded-lg border shadow-md md:min-w-[450px]">
<CommandInput placeholder="Type a term with no match to see the empty state…" />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Actions">
<CommandItem>
New file
<CommandShortcut>⌘N</CommandShortcut>
</CommandItem>
<CommandItem disabled>
Save (no changes)
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
<CommandItem>
Duplicate
<CommandShortcut>⌘D</CommandShortcut>
</CommandItem>
<CommandItem disabled>Delete (locked)</CommandItem>
</CommandGroup>
</CommandList>
</Command>
)
}
Keyboard footer
An app-grade palette with a sticky footer of [Kbd](/components/kbd) hints — ↵ to select, arrows to navigate, esc to close.
Packages
Props
No props documented yet.
import { CornerDownLeftIcon } from "lucide-react"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import { Kbd } from "@/components/ui/kbd"
export default function CommandFooter01() {
return (
<Command className="rounded-lg border shadow-md md:min-w-[450px]">
<CommandInput placeholder="Type a command or search…" />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Pages">
<CommandItem>Dashboard</CommandItem>
<CommandItem>Projects</CommandItem>
<CommandItem>Team members</CommandItem>
<CommandItem>Settings</CommandItem>
</CommandGroup>
</CommandList>
<div className="flex items-center justify-between gap-2 border-t px-3 py-2 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<Kbd>
<CornerDownLeftIcon className="size-3" />
</Kbd>
<span>to select</span>
</div>
<div className="flex items-center gap-1">
<Kbd>↑</Kbd>
<Kbd>↓</Kbd>
<span>to navigate</span>
<span className="mx-1">·</span>
<Kbd>esc</Kbd>
<span>to close</span>
</div>
</div>
</Command>
)
}
The Shadcn Command is the command menu and ⌘K palette — the canonical shadcn/ui command built on cmdk, with fuzzy search, keyboard navigation, grouping, and a CommandDialog for the palette. Official ships a basic menu and a ⌘K demo; the examples below cover what real apps need — nested pages, custom filtering, async search, and a Popover combobox.
Built on cmdk
The Command is a thin, styled wrapper over cmdk, Paco Coursey's command-menu library. cmdk owns the hard parts — fuzzy filtering and ranking, full keyboard navigation, grouping, and the accessibility wiring — so the shadcn layer only adds Tailwind styling and your design tokens. Install it once:
npm install cmdk
Every example below — the palette, nested pages, custom filters, async search, the combobox — is cmdk surfaced through the shadcn parts (Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandShortcut). (Prefer a heavier, action-registry approach? Libraries like kbar exist — but for most apps cmdk + this Command is lighter and matches your design system.)
The ⌘K command palette
The palette is the marquee use. Wrap your list in CommandDialog, hold its open state, and register a global keydown listener:
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setOpen((open) => !open)
}
}
document.addEventListener("keydown", down)
return () => document.removeEventListener("keydown", down)
}, [])
CommandDialog renders the Command inside a Dialog with an sr-only title and description so it's announced to screen readers. The ⌘K command palette example is the complete version, with a Kbd badge in the trigger.
Inline, Dialog & Combobox
The same Command renders three ways depending on its container:
- Inline — drop
Commandstraight into the page for an always-visible menu (the Command menu example). - Dialog — wrap it in
CommandDialogfor the ⌘K palette. - Combobox — put it inside a Popover anchored to a button for a searchable select (the Combobox example).
Custom filtering & keywords
cmdk filters on each item's value by default. To match synonyms, add a keywords array to a CommandItem; to control ranking yourself, pass a filter to Command:
<Command filter={(value, search, keywords) => /* return a score, 0 = hide */}>
The Custom filter & keywords example makes searching "appearance" surface "Toggle theme".
Nested pages (sub-commands)
Real palettes drill in — "Change theme…" opens a Light/Dark/System sub-list. Keep a stack of page names in state: render root items when it's empty, a sub-page's items when its name is on top, and push the next page from onSelect. Pop on Escape or on Backspace when the search is empty. The Nested pages example is the full pattern.
Async / server-side search
For server search, set shouldFilter={false} so cmdk stops filtering locally, drive CommandInput as a controlled value, debounce a request, and render the results you receive — with a loading row in between. The Async / server search example simulates the full round-trip.
Keyboard & accessibility
cmdk handles arrow-key navigation, Enter to select, and type-ahead out of the box; items expose data-selected / data-disabled for styling. Always give CommandDialog a title (it's sr-only by default) so the palette is announced. The Keyboard footer example adds visible Kbd hints — ↵ to select, ↑↓ to navigate, esc to close.
Theming
The Command reads your design tokens — --popover, --accent (the highlighted item), --muted-foreground (headings and shortcuts) — so it follows your theme, including dark mode, with no overrides.