cmdk

repository·main·Indexed 12 days ago

https://github.com/dip/cmdk

A highly composable React command menu component that functions as an accessible combobox. It provides automatic filtering, sorting, and keyboard navigation for building complex search interfaces or command palettes. Requires React 18 and is designed as a Client Component.

Tokens
3.4K
Snippets
10
Records
18
Agent score
46%

What's inside cmdk

  1. Implement nested navigation (Pages) in Command menu

    main

    You can implement a 'drill-down' or 'pages' pattern by managing a stack of items in your state. When an item is selected, push a new 'page' to the stack. Use the onKeyDown prop on the Command root to handle Escape or Backspace (when search is empty) to pop the stack and return to the previous page.

    const [pages, setPages] = React.useState([])
    const page = pages[pages.length - 1]
    
    return (
      <Command
        onKeyDown={(e) => {
          if (e.key === 'Escape' || (e.key === 'Backspace' && !search)) {
            e.preventDefault()
            setPages((pages) => pages.slice(0, -1))
          }
        }}
      >
        <Command.Input value={search} onValueChange={setSearch} />
        <Command.List>
          {!page && (
            <>
              <Command.Item onSelect={() => setPages([...pages, 'projects'])}>Search projects…</Command.Item>
            </>
          )}
          {page === 'projects' && (
            <>
              <Command.Item>Project A</Command.Item>
            </>
          )}
        </Command.List>
      </Command>
    )
  2. Use the Command component and its subcomponents

    main

    The cmdk library provides a set of components to build command menus (like Spotlight or Raycast). The core components are Command, CommandInput, CommandList, CommandItem, CommandGroup, CommandSeparator, CommandEmpty, CommandLoading, and CommandDialog.

    Common usage pattern:

    1. Wrap everything in Command.
    2. Use CommandInput for the search field.
    3. Use CommandList to contain the results.
    4. Use CommandItem for individual selectable entries.
    5. Use CommandGroup to categorize items.
    import { Command } from 'cmdk'
    
    function MyCommandMenu() {
      return (
        <Command>
          <CommandInput placeholder="Type a command..." />
          <CommandList>
            <CommandGroup heading="Suggestions">
              <CommandItem onSelect={() => console.log('Selected!')}>Item 1</CommandItem>
              <CommandItem onSelect={() => console.log('Selected!')}>Item 2</CommandItem>
            </CommandGroup>
          </CommandList>
        </Command>
      )
    }
  3. Troubleshoot cmdk common issues

    main

    If you encounter unexpected behavior, check the following common causes:

    • Hydration Mismatches: Ensure the open prop passed to Command.Dialog is set to false on the server to prevent mismatches during hydration.
    • Incorrect Item Behavior: Ensure every Command.Item has a unique key and a unique value prop.
    • Performance/Filtering: If you want to manage filtering or sorting manually (e.g., to implement your own virtualization), pass shouldFilter={false} to the Command component.
    • React Environment: cmdk is a Client Component and requires React 18 (it uses useId and useSyncExternalStore). It is not compatible with React Native.
  4. Basic usage of the Command component

    main

    The Command component acts as the root for your command menu. It provides a composable API where you can include an input, a list of items, groups, and empty states. Items are automatically filtered and sorted based on the input.

    import { Command } from 'cmdk'
    
    const CommandMenu = () => {
      return (
        <Command label="Command Menu">
          <Command.Input />
          <Command.List>
            <Command.Empty>No results found.</Command.Empty>
    
            <Command.Group heading="Letters">
              <Command.Item>a</Command.Item>
              <Command.Item>b</Command.Item>
              <Command.Separator />
              <Command.Item>c</Command.Item>
            </Command.Group>
    
            <Command.Item>Apple</Command.Item>
          </Command.List>
        </Command>
      )
    }
  5. Use Command.Dialog for elevated command menus

    main

    To render the command menu in a modal/dialog context, use Command.Dialog. This component composes Radix UI's Dialog and includes an overlay. It can be controlled using the open and onOpenChange props. You can also specify a container prop to control where the Dialog portals into.

    import { Command } from 'cmdk'
    
    const CommandMenu = () => {
      const [open, setOpen] = React.useState(false)
    
      return (
        <Command.Dialog open={open} onOpenChange={setOpen} label="Global Command Menu">
          <Command.Input />
          <Command.List>
            <Command.Empty>No results found.</Command.Empty>
            <Command.Item>Apple</Command.Item>
          </Command.List>
        </Command.Dialog>
      )
    }
  6. Use useCommandState for advanced state access

    main

    The useCommandState hook allows you to access slices of the command menu state (like the current search string or selectedField). It is built on useSyncExternalStore and should be used sparingly for advanced UI logic, such as customizing the empty state based on the current search query.

    const search = useCommandState((state) => state.search)
    return <Command.Empty>No results found for "{search}".</Command.Empty>
  7. Configure Command.Item filtering with keywords

    main

    Each Command.Item can have a unique value. If not provided, it is inferred from the .textContent. You can provide a keywords array to help with filtering; these act as aliases for the item value and are also trimmed.

    <Command.Item
      onSelect={(value) => console.log('Selected', value)}
      keywords={['fruit', 'apple']}
    >
      Apple
    </Command.Item>
  8. Manually filter or sort items

    main

    By default, cmdk handles filtering. To implement your own filtering or sorting logic (which is recommended for better memory usage and performance when using custom virtualization), pass shouldFilter={false} to the Command component.

    <Command shouldFilter={false}>
      {/* Your custom filtering logic here */}
    </Command>
  9. Configure Command filtering and sorting

    main

    The Command root component allows you to customize how items are ranked and filtered.

    • Custom Filter: Provide a filter function. It receives (value, search, keywords). Note that value is always trimmed.
    • Disable Filtering: Set shouldFilter={false} to manage filtering manually.
    • Looping: Set the loop prop to make arrow key navigation wrap around the list.
    • Value Control: Use value and onValueChange to control the selected item.
    // Custom filter with keywords
    <Command
      filter={(value, search, keywords) => {
        const extendValue = value + ' ' + keywords.join(' ')
        if (extendValue.includes(search)) return 1
        return 0
      }}
    />
    
    // Disable automatic filtering
    <Command shouldFilter={false}>
      <Command.List>
        {filteredItems.map((item) => (
          <Command.Item key={item} value={item}>
            {item}
          </Command.Item>
        ))}
      </Command.List>
    </Command>
  10. Reference: Command component parts and data attributes

    main

    All parts forward props (including ref) to the underlying elements. Use the following data-attribute prefixes for styling:

    • Command root: [cmdk-root]
    • Command.Dialog: [cmdk-dialog]
    • Command.Overlay: [cmdk-overlay]
    • Command.Input: [cmdk-input]
    • Command.List: [cmdk-list]
    • Command.Item: [cmdk-item] (also supports [data-disabled?] and [data-selected?])
    • Command.Group: [cmdk-group] (also supports [hidden?])
    • Command.Separator: [cmdk-separator]
    • Command.Empty: [cmdk-empty]
    • Command.Loading: [cmdk-loading]
  11. Configure CommandGroup

    main

    The CommandGroup component groups items together.

    • heading (ReactNode): An optional heading to render for the group.
    • value (string): If no heading is provided, you must provide a unique value for the group.
    • forceMount (boolean): If true, the group is always rendered regardless of filtering.