React Command Component Examples
Drop-in React Command examples: cmdk-based palette with groups, dialog with built-in Cmd+K shortcut, custom filter, loading state, and shortcut keys.
A command palette is how power users move through an app without touching the mouse — the ⌘K overlay you already know from Linear, GitHub, and VS Code. Drivn's Command component is a styling layer over cmdk, the same primitive shadcn/ui builds on. cmdk owns the parts that are genuinely hard: scoring items against the search string, moving selection with the arrow keys, wiring the combobox ARIA roles, and keeping a list of several hundred rows responsive while the user types.
What Drivn adds is the surface you actually touch. Dot-notation subcomponents — Command.Input, Command.List, Command.Group, Command.Item — a card-shaped root, and a Command.Dialog that mounts the palette inside Drivn's zero-runtime-dependency Dialog with the ⌘K listener already attached. Run npx drivn add command and the CLI writes the file to @/components/ui/command, which is the import path every snippet on this page uses verbatim.
The five examples below are the ones that keep showing up in shipped products: a grouped inline palette, the dialog variant, a custom filter for matching on fields the user never sees, a loading state for async results, and shortcut hints rendered on each row. Start from whichever is closest and swap the items for your own. For API and bundle differences against the shadcn/ui equivalent, read Drivn vs shadcn Command.
Basic palette with groups
The default Command renders a search input followed by a scrollable list of items grouped under headings. cmdk filters items as the user types — by default against the visible text content, falling back to any keywords array you pass on Command.Item. Render Command.Empty as a sibling to display a "No results found." fallback when nothing matches; pass children to override the copy. The component is uncontrolled by default and tracks the current selection internally, so the snippet below works without any state management. The structure mirrors the Command docs usage and matches what npx drivn add command writes into your project.
1 import { Command } from "@/components/ui/command" 2 import { Calendar, Search, Smile, User, CreditCard, Settings } from "lucide-react" 3 4 export default function Page() { 5 return ( 6 <Command> 7 <Command.Input placeholder="Type a command..." /> 8 <Command.List> 9 <Command.Empty /> 10 <Command.Group heading="Suggestions"> 11 <Command.Item> 12 <Calendar className="w-4 h-4" /> 13 Calendar 14 </Command.Item> 15 <Command.Item> 16 <Search className="w-4 h-4" /> 17 Search 18 </Command.Item> 19 <Command.Item> 20 <Smile className="w-4 h-4" /> 21 Emoji 22 </Command.Item> 23 </Command.Group> 24 <Command.Separator /> 25 <Command.Group heading="Settings"> 26 <Command.Item> 27 <User className="w-4 h-4" /> 28 Profile 29 </Command.Item> 30 <Command.Item> 31 <CreditCard className="w-4 h-4" /> 32 Billing 33 </Command.Item> 34 <Command.Item> 35 <Settings className="w-4 h-4" /> 36 Preferences 37 </Command.Item> 38 </Command.Group> 39 </Command.List> 40 </Command> 41 ) 42 }
Dialog with built-in Cmd+K shortcut
Command.Dialog is the palette most apps ship: the same Command primitive rendered inside Drivn's Dialog, with p-0 max-w-lg rounded-[10px] on the dialog content so the search input sits flush against the modal edge. The ⌘K binding is not something you wire up. A React.useEffect inside the component registers a keydown listener on document, matches e.key === 'k' && (e.metaKey || e.ctrlKey), calls preventDefault() so the browser's own shortcut does not fire, then toggles via onOpenChange(!open). The effect returns a cleanup that removes the listener, so unmounting a route never leaves a dangling handler behind.
Your side of the contract is two props, both required: open and onOpenChange. That keeps the palette controlled, which matters because you usually close it from an item handler right after routing. Ctrl+K works alongside ⌘K from the same condition, so Windows and Linux users get the binding with no platform check. The optional label prop is forwarded to the cmdk root as its accessible name — set something descriptive like "Command Menu" so screen readers announce the overlay instead of an unnamed dialog.
1 import { useState } from "react" 2 import { Command } from "@/components/ui/command" 3 import { Calendar, Search, Smile, User, CreditCard, Settings } from "lucide-react" 4 5 export default function Page() { 6 const [open, setOpen] = useState(false) 7 8 return ( 9 <Command.Dialog 10 open={open} 11 onOpenChange={setOpen} 12 label="Command Menu" 13 > 14 <Command.Input placeholder="Search..." /> 15 <Command.List> 16 <Command.Empty /> 17 <Command.Group heading="Suggestions"> 18 <Command.Item> 19 <Calendar className="w-4 h-4" /> 20 Calendar 21 </Command.Item> 22 <Command.Item> 23 <Search className="w-4 h-4" /> 24 Search 25 </Command.Item> 26 <Command.Item> 27 <Smile className="w-4 h-4" /> 28 Emoji 29 </Command.Item> 30 </Command.Group> 31 <Command.Separator /> 32 <Command.Group heading="Settings"> 33 <Command.Item> 34 <User className="w-4 h-4" /> 35 Profile 36 </Command.Item> 37 <Command.Item> 38 <CreditCard className="w-4 h-4" /> 39 Billing 40 </Command.Item> 41 <Command.Item> 42 <Settings className="w-4 h-4" /> 43 Preferences 44 </Command.Item> 45 </Command.Group> 46 </Command.List> 47 </Command.Dialog> 48 ) 49 }
Custom filter function
By default, cmdk filters items by checking the search string against each item's text content. For richer matching — fuzzy scoring, alias lookup, or matching against fields the user does not see — pass a filter prop with the signature (value: string, search: string) => number. Return 1 for a match, 0 to hide the item, or a value in between for ranked results. The example below filters case-insensitively. You can also pass keywords on Command.Item (an array of strings) to extend the searchable text without changing the visible label, which is useful for synonyms — adding ['log out'] to a "Sign out" item, or ['preferences'] to a "Settings" item. See the Command docs for the full prop list.
1 import { Command } from "@/components/ui/command" 2 3 <Command 4 filter={(value, search) => { 5 if (value.toLowerCase().includes(search.toLowerCase())) { 6 return 1 7 } 8 return 0 9 }} 10 > 11 <Command.Input placeholder="Search..." /> 12 <Command.List> 13 <Command.Empty /> 14 <Command.Item keywords={['preferences']}>Settings</Command.Item> 15 <Command.Item keywords={['log out']}>Sign out</Command.Item> 16 </Command.List> 17 </Command>
Async loading state
For palettes that fetch results from an API — search-as-you-type over a database, autocomplete on remote data, a help search hitting an indexer — show a loading state while the request is in flight. Command.Loading renders a centered muted message inside the list area. Toggle it via React state controlled from your async effect, and replace the items with the response when it returns. Pass children for the visible copy — "Searching…" is a safe default. Combine it with Command.Empty so users see the right message whether the request is pending or returned no results, and disable cmdk's built-in filtering with shouldFilter={false} so the API response is rendered as-is. After the basic installation, the same imports work for both inline and dialog variants.
1 import { useState, useEffect } from "react" 2 import { Command } from "@/components/ui/command" 3 4 export default function Page() { 5 const [search, setSearch] = useState("") 6 const [items, setItems] = useState<string[]>([]) 7 const [loading, setLoading] = useState(false) 8 9 useEffect(() => { 10 if (!search) return 11 setLoading(true) 12 fetch("/api/search?q=" + encodeURIComponent(search)) 13 .then((r) => r.json()) 14 .then((data) => { 15 setItems(data.items) 16 setLoading(false) 17 }) 18 }, [search]) 19 20 return ( 21 <Command shouldFilter={false}> 22 <Command.Input 23 value={search} 24 onValueChange={setSearch} 25 placeholder="Search..." 26 /> 27 <Command.List> 28 {loading && <Command.Loading>Searching…</Command.Loading>} 29 <Command.Empty /> 30 {items.map((item) => ( 31 <Command.Item key={item}>{item}</Command.Item> 32 ))} 33 </Command.List> 34 </Command> 35 ) 36 }
Shortcut keys
Command.Shortcut renders a <kbd> element styled with text-xs tracking-widest text-muted-foreground aligned to the right of the parent item via ml-auto. It is purely visual — there is no key handling. Pair it with the actual keyboard handler in your app's global hotkey logic. The shortcut text supports any string, including Unicode glyphs like ⌘ for the macOS Command key, ⌃ for Control, ⇧ for Shift, ⌥ for Option. The example below renders ⌘C, ⌘K, ⌘E, ⌘P, ⌘B, and ⌘S next to each item, matching how editor and admin apps label their shortcut affordances inside the Command docs.
1 <Command> 2 <Command.Input placeholder="Search..." /> 3 <Command.List> 4 <Command.Empty /> 5 <Command.Group heading="Suggestions"> 6 <Command.Item> 7 <Calendar className="w-4 h-4" /> 8 Calendar 9 <Command.Shortcut>⌘C</Command.Shortcut> 10 </Command.Item> 11 <Command.Item> 12 <Search className="w-4 h-4" /> 13 Search 14 <Command.Shortcut>⌘K</Command.Shortcut> 15 </Command.Item> 16 <Command.Item> 17 <Smile className="w-4 h-4" /> 18 Emoji 19 <Command.Shortcut>⌘E</Command.Shortcut> 20 </Command.Item> 21 </Command.Group> 22 <Command.Separator /> 23 <Command.Group heading="Settings"> 24 <Command.Item> 25 <User className="w-4 h-4" /> 26 Profile 27 <Command.Shortcut>⌘P</Command.Shortcut> 28 </Command.Item> 29 <Command.Item> 30 <CreditCard className="w-4 h-4" /> 31 Billing 32 <Command.Shortcut>⌘B</Command.Shortcut> 33 </Command.Item> 34 <Command.Item> 35 <Settings className="w-4 h-4" /> 36 Preferences 37 <Command.Shortcut>⌘S</Command.Shortcut> 38 </Command.Item> 39 </Command.Group> 40 </Command.List> 41 </Command>
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
No. The Command source imports Command as CommandPrimitive from cmdk and the local Dialog from @/components/ui/dialog. Drivn's Dialog is zero-runtime-deps — no Radix, no floating-ui — so the only runtime cost beyond React itself is cmdk. shadcn/ui uses cmdk plus Radix Dialog for its CommandDialog; Drivn skips the Radix dependency entirely, which keeps the bundle smaller in projects that do not already pull Radix in for other components.
By default, cmdk scores each item's text content against the search string using a fuzzy match algorithm. Items with a non-zero score render in the filtered list; items with zero score are hidden. Pass a keywords array on Command.Item to add searchable text that does not appear in the UI — useful for aliases like ['log out'] on a "Sign out" item or ['preferences'] on a "Settings" item.
Yes. Render <Command> directly inline — inside a card, a sidebar, or a popover trigger you assemble yourself. The Dialog variant is the right choice for app-wide command palettes opened with Cmd+K, but the bare Command works fine inside a search panel or any other container. The same items, groups, and filtering logic apply because the Dialog only adds the modal wrapper plus the Cmd+K listener.
The Command file starts with "use client" because cmdk uses useState and event listeners. You can import it from a Server Component, but it renders on the client side of the component boundary. Next.js draws the boundary correctly based on the directive at the top of the file, so you do not need to mark your Server Component with "use client" just because it imports Command somewhere in its tree.
Pass shouldFilter={false} on the Command root. cmdk will then render whatever items you put inside Command.List without scoring them against the search query, which is the right setting when you fetch matched items from an API and want full control over the rendered list. Combined with controlled value and onValueChange on Command.Input, this gives you a fully external filtering pipeline.

