Skip to content
Drivn logoDrivn
5 min read

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 menu — also called a command palette — is the keyboard-driven search overlay popularized by VS Code, Linear, and GitHub. The Command component in Drivn is a thin styling layer over cmdk, the same primitive shadcn/ui builds on. cmdk does the heavy work: fuzzy search filtering, keyboard navigation, ARIA roles, and the rendering tricks that make filtered lists feel instant on hundreds of items.

This page collects the patterns that ship in real applications. Each snippet imports from @/components/ui/command, the same path the Drivn CLI writes the file to, so you can paste them into a Next.js or Vite project after npx drivn add command and they compile without edits. The Command source itself lives in your repo after install — the runtime cost is one cmdk dependency plus a few hundred lines of plain TSX you own end to end.

The five examples below cover the common cases: a basic palette with groups, the dialog variant with the built-in Cmd+K listener, a custom filter for non-text matching, a loading state for async results, and shortcut keys rendered alongside each item. Pick the one closest to your use case and change the items to whatever your app needs to expose. If you also want to compare the API and bundle cost against the shadcn/ui equivalent, see 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.

1import { Command } from "@/components/ui/command"
2import { Calendar, Search, Smile, User, CreditCard, Settings } from "lucide-react"
3
4export 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 mounts the same Command primitive inside a Drivn Dialog and wires the Cmd+K keyboard shortcut for you. The implementation registers a keydown listener on document, checks for the k key plus metaKey || ctrlKey, and toggles the dialog's open state. You only manage open in your own React state; the component handles the listener cleanup on unmount, so there are no stale handlers when the parent component unmounts. Pass a label prop for the accessible name announced by screen readers — cmdk forwards it as aria-label on the command root.

1import { useState } from "react"
2import { Command } from "@/components/ui/command"
3import { Calendar, Search, Smile, User, CreditCard, Settings } from "lucide-react"
4
5export 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.

1import { 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.

1import { useState, useEffect } from "react"
2import { Command } from "@/components/ui/command"
3
4export 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>&#8984;C</Command.Shortcut>
10 </Command.Item>
11 <Command.Item>
12 <Search className="w-4 h-4" />
13 Search
14 <Command.Shortcut>&#8984;K</Command.Shortcut>
15 </Command.Item>
16 <Command.Item>
17 <Smile className="w-4 h-4" />
18 Emoji
19 <Command.Shortcut>&#8984;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>&#8984;P</Command.Shortcut>
28 </Command.Item>
29 <Command.Item>
30 <CreditCard className="w-4 h-4" />
31 Billing
32 <Command.Shortcut>&#8984;B</Command.Shortcut>
33 </Command.Item>
34 <Command.Item>
35 <Settings className="w-4 h-4" />
36 Preferences
37 <Command.Shortcut>&#8984;S</Command.Shortcut>
38 </Command.Item>
39 </Command.Group>
40 </Command.List>
41</Command>
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

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.