Command

A command palette component for quick actions, search, and keyboard navigation

Installation

Terminal
pnpm add @choice-ui/command

Import

Component.tsx
import { CommandRoot, defaultFilter, Command, useCommandState, useCommand, useValue, commandScore, type CommandProps, type CommandGroupProps, type CommandItemProps, type CommandListProps, type CommandLoadingProps, type CommandDividerProps, type CommandInputProps } from "@choice-ui/command"

Basic

**Basic Command Menu**
A simple command menu with search functionality and basic items. Demonstrates the core structure with input, list, and items.
```tsx <Command> <Command.Input placeholder="Type a command..." /> <Command.List> <Command.Item>Action 1</Command.Item> <Command.Item>Action 2</Command.Item> </Command.List> </Command> ```
Calendar
Search Emoji
Calculator
Settings
Profile

With Prefix And Suffix

**With Prefix and Suffix**
Shows how to use prefix and suffix elements in a command item.
Photoshop
New
Illustrator
Lightroom
New
InDesign
New

With Shortcut

**With Shortcut**
Shows how to use a shortcut in a command item.
```tsx <Command.Item shortcut={{ keys: "F", modifier: "command" }}> <Command.Value>Photoshop</Command.Value> </Command.Item> ```
Photoshop
P
Illustrator
I
Lightroom
L
InDesign
D

With Groups

**Grouped Commands**
Organizes commands into logical groups with headings and separators. Perfect for categorizing different types of actions or content.

Large

With Empty State

**With Empty State**
Shows an empty state when no results match the search query. Essential for providing user feedback during search operations.
```tsx <Command> <Command.Input /> <Command.List> <Command.Empty>No results found.</Command.Empty> <Command.Item>...</Command.Item> </Command.List> </Command> ```

Dialog Mode

Dark Mode

Disabled Command

**Disabled Command**
Shows a disabled command with a disabled item. Useful for providing user feedback during search operations.
```tsx <Command> <Command.Input /> <Command.List> <Command.Item disabled>...</Command.Item> </Command.List> </Command> ```

Controlled State

**Controlled State**
Demonstrates controlled command state with external value management. Useful for programmatic control and integration with other components.
```tsx const [value, setValue] = useState("") <Command value={value} onChange={setValue}> <Command.Input /> <Command.List> <Command.Item value="item1">Item 1</Command.Item> </Command.List> </Command> ```

Selected: profile

Search: None

With Custom Filtering

**With Custom Filtering**
Shows custom filtering logic and disabled filtering for manual control. Useful when you need to implement custom search algorithms or server-side filtering.
```tsx const customFilter = (value, search) => { return search.length > 0 ? value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0 : 1 } <Command filter={customFilter}> <Command.Input /> <Command.List>...</Command.List> </Command> ```

Custom Filter: Prioritizes exact matches, then prefix matches

Current Search: ""

Keyboard Navigation

**Interactive Keyboard Navigation**
A comprehensive demonstration of cmdk's keyboard navigation system with real-time feedback. This interactive story shows all keyboard shortcuts and provides visual feedback for each action.
**Keyboard Shortcuts:**
Basic Navigation:
- ↑↓ - Navigate between items - Enter - Select current item - Home - Jump to first item - End - Jump to last item
Vim Bindings (toggleable):
- Ctrl+J - Next item - Ctrl+K - Previous item - Ctrl+N - Next item - Ctrl+P - Previous item
Advanced Navigation:
- Alt+↑↓ - Jump between groups - Cmd+↑↓ (Mac) - Jump to first/last item - Loop navigation - When enabled, circular navigation at boundaries (↑ at top goes to bottom)
**Features:**
- IME Support: Proper handling of CJK input methods without triggering shortcuts - Visual Feedback: Real-time activity monitor showing triggered shortcuts
**Usage Tips:**
- Focus the input first to activate keyboard navigation - Use Alt+↑↓ to quickly jump between different sections - Toggle vim bindings for familiar Ctrl+J/K/N/P shortcuts - Enable loop navigation for circular item navigation
```tsx <Command vimBindings={true} loop={true}> <Command.Input /> <Command.List> <Command.Group heading="Files"> <Command.Item>New File</Command.Item> </Command.Group> </Command.List> </Command> ```
Selected: NoneTry all keyboard shortcuts! 🎹

Large Dataset

**Large Dataset**
Performance test with a large number of items to demonstrate efficient rendering and search capabilities.
```tsx <Command> <Command.Input /> <Command.List> {largeItemList.map(item => ( <Command.Item key={item.id} value={item.name}> {item.name} </Command.Item> ))} </Command.List> </Command> ```

Dataset Size: 100 items across 3 categories

Performance: Try searching to see fast filtering in action

Complex Items

**Complex Items**
Demonstrates rich item content with metadata, badges, and custom layouts. Perfect for file browsers, user selectors, or detailed action lists.
```tsx <Command.Item value="complex-item"> <div className="flex items-center justify-between w-full"> <div className="flex items-center"> <Icon /> <div> <div>Title</div> <div className=" text-secondary-foreground">Subtitle</div> </div> </div> <Badge>New</Badge> </div> </Command.Item> ```

Core Mechanism Store

**Core Mechanism: Store & State Management**
Demonstrates the centralized state management system that powers cmdk. Shows how search, selection, and filtering states are managed centrally.
**Key Features:**
- Centralized state store with subscribe/emit pattern - Real-time state synchronization across components - Optimized updates with value comparison guards - Async scheduling to prevent infinite loops
**How it works:** The store uses a centralized state with subscribe/emit pattern. Each component subscribes to state changes and re-renders only when necessary.
**Try it:**
- Type in the input to see search query and filtered count change - Use arrow keys to navigate - watch selectedItemId update - Press Enter to select - watch value update - Filter items - watch visible groups change
```tsx // Access specific state values using selectors const search = useCommandState((state) => state.search) const value = useCommandState((state) => state.value) const filteredCount = useCommandState((state) => state.filtered.count) const filteredGroups = useCommandState((state) => state.filtered.groups) // State structure: // { search, value, selectedItemId, filtered: { count, items, groups } } ```

Internal State Monitor

Search Query: ""
Selected Value: "(none)"
Selected Item ID: (none)
Filtered Count: 0
Update Count: 0
Visible Groups: [none]

Core Mechanism Value Registration

**Core Mechanism: Value Registration System**
Demonstrates how Command.Item and Command.Group automatically extract and register values using the useValue hook internally. Shows value extraction from props, children, and DOM content.
**Key Features:**
- Automatic value extraction from content or props - Real-time registration without dependency arrays - Keyword aliases for enhanced search matching - DOM attribute synchronization (data-value) for CSS selectors
**How it works:**
- Command.Item and Command.Group internally call useValue() hook - useValue() runs on every render (no deps array) to keep values synchronized - It extracts value from: 1) value prop, 2) children text content, 3) DOM textContent - Values are registered with the global store for search and filtering - DOM elements get a data-value attribute for CSS selector targeting
**Internal implementation:**
```tsx // Inside Command.Item component: const valueDeps = useMemo(() => [value, children, ref], [value, children]) const valueRef = useValue(id, ref, valueDeps, keywords) ```
**Usage:**
```tsx // You don't need to call useValue directly - it's used internally: <Command.Item value="explicit-value">Content</Command.Item> <Command.Item>Content from children</Command.Item> <Command.Item value="file" keywords={["doc", "document"]}>File</Command.Item> ```
Items: 3

Core Mechanism Async Scheduling

**Core Mechanism: Async Scheduling System**
Demonstrates the scheduling system that prevents infinite loops and optimizes updates. Command uses `useScheduleLayoutEffect` to batch operations and execute them in the next layout effect cycle.
**Why scheduling is needed:**
- Prevents infinite loops: When operations trigger state changes that trigger more operations - Batches updates: Multiple operations scheduled with the same ID are deduplicated - Optimizes DOM updates: All operations execute together in one layout effect cycle - Breaks recursive cycles: Defers execution to avoid synchronous recursion
**How it works:**
- `schedule(id, callback)` stores callbacks in a Map keyed by ID - Operations with the same ID replace previous ones (deduplication) - All scheduled callbacks execute together in the next `useIsomorphicLayoutEffect` - After execution, the Map is cleared
**Common scheduling priorities:**
- Priority 1: `selectFirstItem()` - High priority, runs first - Priority 2: `sort()` + `store.emit()` - Value registration - Priority 3: `filterItems()` + `sort()` - Item mount/unmount - Priority 4: `filterItems()` - Item removal - Priority 5: `scrollIntoView()` - Scroll operations - Priority 7: `updateSelection()` - Selection updates
**What happens when you type:**
1. Search state updates synchronously 2. `schedule(1, selectFirstItem)` - Queued 3. `filterItems()` - Runs immediately 4. `sort()` - Runs immediately 5. All scheduled operations execute in next layout effect
**What happens when item mounts:**
1. Item registers with store 2. `schedule(3, filterItems + sort)` - Queued 3. If no value selected, `selectFirstItem()` runs 4. All operations batch together in layout effect
**Benefits:**
- Prevents infinite loops from recursive updates - Batches multiple operations into one DOM update cycle - Deduplicates operations with same priority ID - Optimizes performance by reducing layout thrashing
```tsx // Operations are scheduled and batched: schedule(1, selectFirstItem) // High priority schedule(5, scrollIntoView) // Medium priority schedule(7, updateSelection) // Low priority // All execute together in next layout effect ```

Nested Items

**Example: Nested Items / Pages**
Demonstrates navigation between different "pages" of items using state management. Shows how to implement drill-down navigation where selecting one item shows a deeper set of items.
**Key Features:**
- Page stack management with state - Escape/Backspace to go back to previous page - Conditional rendering based on current page - Dynamic page navigation
```tsx const [pages, setPages] = useState([]) const page = pages[pages.length - 1] // Navigate deeper <Command.Item onSelect={() => setPages([...pages, 'projects'])}> Search projects… </Command.Item> ```
Navigation Guide:
  • • Select items to navigate deeper into subcategories
  • or (when search is empty) to go back
  • • Current path: Home

Conditional Sub Items

**Example: Conditional Sub-Items**
Shows how to conditionally render sub-items that only appear when searching. Useful for revealing detailed actions only when the user is actively searching.
**Key Features:**
- Sub-items only visible during search - Custom SubItem component using useCommandState - Progressive disclosure pattern - Search-driven UI expansion
```tsx const SubItem = (props) => { const search = useCommandState((state) => state.search) if (!search) return null return <Command.Item {...props} /> } ```
Search Behavior:
  • Normal view: Shows only main items
  • When searching: Sub-items become visible
  • • Try searching for "theme", "dark", or "notification" to see sub-items appear

Async Results

**Example: Asynchronous Results**
Demonstrates loading data asynchronously and displaying results as they become available. Shows proper loading states and handles dynamic content updates.
**Key Features:**
- Async data fetching simulation - Loading states with Command.Loading - Dynamic item rendering - Automatic filtering and sorting
```tsx const [loading, setLoading] = useState(false) const [items, setItems] = useState([]) useEffect(() => { async function fetchData() { setLoading(true) const data = await api.get('/items') setItems(data) setLoading(false) } fetchData() }, []) ```
Async Data Loading
Status: 0 items loaded

With Tabs

**Example: Command with Tabs Filter**
Demonstrates how to use Command.Tabs for quick filtering of items by category. The first tab is always "All" to show all items, followed by category-specific filters.
**Key Features:**
- Tab-based filtering system - "All" tab shows all items - Category-specific filtering - Preserves search functionality within filters - Integrated with Command's existing filtering system
```tsx <Command.Tabs value={activeTab} onChange={setActiveTab}> <Tabs.Item value="all">All</Tabs.Item> <Tabs.Item value="files">Files</Tabs.Item> <Tabs.Item value="actions">Actions</Tabs.Item> </Command.Tabs> ```
Tabbed Filtering:
  • • Use tabs to quickly filter items by category
  • • "All" tab shows all items across categories
  • • Search works within the selected tab filter
  • • Use left and right arrow keys to cycle through tabs
  • • Currently showing: All Items
  • • Items visible: 9
Showing all categories
9 items

API reference

CommandPropsTypeDefault
defaultValue
string
|
undefined
-
disablePointerSelection
boolean
|
undefined
-
filter
CommandFilter
|
undefined
-
label
string
|
undefined
-
loop
boolean
|
undefined
-
onChange
((value: string) => void)
|
undefined
-
shouldFilter
boolean
|
undefined
-
size
undefined
|
"default"
|
"large"
"default"
value
string
|
undefined
-
variant
undefined
|
"default"
|
"dark"
"default"
vimBindings
boolean
|
undefined
-
CommandGroupPropsTypeDefault
forceMount
boolean
|
undefined
false
heading
ReactNode -
value
string
|
undefined
-
hidden
boolean
|
undefined
false
ref
((instance: HTMLDivElement
|
null) => void)
|
RefObject<HTMLDivElement>
|
null
|
undefined
-
CommandItemPropsTypeDefault
onSelect
((value: string) => void)
|
undefined
-
disabled
boolean
|
undefined
-
value
string
|
undefined
-
forceMount
boolean
|
undefined
-
keywords
string[]
|
undefined
-
prefixElement
ReactNode -
shortcut
{ keys?: ReactNode; modifier?: KbdKey
|
KbdKey[]
|
undefined; }
|
undefined
-
suffixElement
ReactNode -
ref
((instance: HTMLDivElement
|
null) => void)
|
RefObject<HTMLDivElement>
|
null
|
undefined
-
CommandListPropsTypeDefault
children
ReactNode -
className
string
|
undefined
-
label
string
|
undefined
-
aria-label
string
|
undefined
-
aria-labelledby
string
|
undefined
-
hoverBoundary
undefined
|
"none"
|
"hover"
-
orientation
undefined
|
"vertical"
|
"horizontal"
|
"both"
-
scrollbarMode
undefined
|
"default"
|
"padding-y"
|
"padding-x"
|
"padding-b"
|
"padding-t"
|
"padding-l"
|
"padding-r"
-
type
undefined
|
"hover"
|
"auto"
|
"always"
|
"scroll"
-
variant
undefined
|
"default"
|
"light"
|
"dark"
-
CommandLoadingPropsTypeDefault
label
string
|
undefined
"Loading..."
progress
number
|
undefined
undefined
CommandDividerPropsTypeDefault
alwaysRender
boolean
|
undefined
false
CommandInputPropsTypeDefault
value
string
|
undefined
-
onChange
((search: string) => void)
|
undefined
-
size
undefined
|
"default"
|
"large"
-
selected
boolean
|
undefined
-
className
string
|
undefined
-
focusSelection
undefined
|
"none"
|
"all"
|
"end"
-
onIsEditingChange
((isEditing: boolean) => void)
|
undefined
-
variant
undefined
|
"default"
|
"light"
|
"dark"
|
"reset"
-
prefixElement
ReactNode -
suffixElement
ReactNode -