Skip to main content

command

A fast, composable, unstyled command menu for React applications.

Features

  • Searchable Command Dialog: Command palette with search and real-time filtering.
  • Groupable Command List: Organize commands into logical groups with optional headings.
  • Selectable Items: Items support click actions, focus-based highlighting, and selection via Enter/Space.
  • Keyboard Navigation: Full keyboard support with arrow keys, Enter, Space, and vim-style bindings (gg/G).
  • Typeahead Search: Type printable characters to jump to matching items in the list.
  • Custom Shortcuts: Optional shortcut labels shown beside each command item.
  • Empty State Handling: Automatically displays a fallback when no items match the query.
  • RTL Support: Full right-to-left layout via local dir override or global DirectionProvider.
  • Accessible & Composable: ARIA roles (listbox, option, combobox, status) and headless architecture for complete flexibility.
  • Responsive Design: Adapts gracefully to different screen sizes and containers.
  • Dialog or Inline Support: Can be used as a floating dialog or embedded inline.

About

This component is built on top of the @gentleduck/primitives command primitive, following the same Radix-style context/collection pattern used by Select and other primitives. The registry-layer components are thin styling wrappers around the primitives.

Direction (dir) is resolved once at the root Command primitive via useDirection and stored in context. Every child primitive — CommandInput, CommandList, CommandItem, CommandGroup, CommandEmpty, and CommandSeparator — reads dir from context and applies it to its DOM element automatically. Use local dir on Command when needed, or set DirectionProvider at app/root level for global direction.

Philosophy

Command palettes are the power user's best friend — they surface every action in the app behind a single keyboard shortcut. The command primitive provides built-in search filtering, group visibility management, keyboard navigation, and typeahead with minimal API surface. The composable sub-components let you build anything from a simple search to a full IDE-style command center.

How It's Built

Loading diagram...

Installation


npx @gentleduck/cli add command

npx @gentleduck/cli add command

Usage

import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from '@/components/ui'
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from '@/components/ui'
<Command>
  <CommandInput placeholder="Type a command or search..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Suggestions">
      <CommandItem>Search GitHub</CommandItem>
      <CommandItem>Search Twitter</CommandItem>
      <CommandItem>Search Discord</CommandItem>
    </CommandGroup>
    <CommandSeparator />
    <CommandGroup heading="Settings">
      <CommandItem>General</CommandItem>
      <CommandItem>Profile</CommandItem>
      <CommandItem>Notifications</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>
<Command>
  <CommandInput placeholder="Type a command or search..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Suggestions">
      <CommandItem>Search GitHub</CommandItem>
      <CommandItem>Search Twitter</CommandItem>
      <CommandItem>Search Discord</CommandItem>
    </CommandGroup>
    <CommandSeparator />
    <CommandGroup heading="Settings">
      <CommandItem>General</CommandItem>
      <CommandItem>Profile</CommandItem>
      <CommandItem>Notifications</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>

Examples

Dialog

To show the command menu in a dialog, use the <CommandDialog /> component, or use the <Button/> component with the command variant.

import { useKeyCommands } from '@gentleduck/vim/react'
 
export function CommandMenu() {
  const [open, setOpen] = React.useState(false)
 
  useKeyCommands(
    {
      'ctrl+j': {
        description: 'Open command menu',
        execute: () => {
          setOpen(true)
        },
        name: 'ctrl+j',
      },
    },
    { preventDefault: true },
  )
 
 
  return (
    <CommandDialog
      open={open}
      onOpenChange={setOpen}
    >
      <CommandInput placeholder="Type a command or search..." />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="Suggestions">
          <CommandItem>Settings</CommandItem>
          <CommandItem>Messages</CommandItem>
          <CommandItem>Search</CommandItem>
        </CommandGroup>
      </CommandList>
    </CommandDialog>
  )
}
import { useKeyCommands } from '@gentleduck/vim/react'
 
export function CommandMenu() {
  const [open, setOpen] = React.useState(false)
 
  useKeyCommands(
    {
      'ctrl+j': {
        description: 'Open command menu',
        execute: () => {
          setOpen(true)
        },
        name: 'ctrl+j',
      },
    },
    { preventDefault: true },
  )
 
 
  return (
    <CommandDialog
      open={open}
      onOpenChange={setOpen}
    >
      <CommandInput placeholder="Type a command or search..." />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="Suggestions">
          <CommandItem>Settings</CommandItem>
          <CommandItem>Messages</CommandItem>
          <CommandItem>Search</CommandItem>
        </CommandGroup>
      </CommandList>
    </CommandDialog>
  )
}

Component Composition

Loading diagram...

API Reference

Command

PropTypeDefaultDescription
dir'ltr' | 'rtl'--Text direction. Resolved by primitives useDirection (dir prop -> DirectionProvider -> 'ltr') and inherited by children.
classNamestring--Additional CSS classes applied to the command container
childrenReact.ReactNode--Command content (CommandInput, CommandList, etc.)
...propsReact.HTMLProps<HTMLDivElement>--Additional props to spread to the root div

CommandInput

PropTypeDefaultDescription
placeholderstring'Search...'Placeholder text in the input
autoFocusbooleanfalseAutomatically focus input on mount
wrapperClassNamestring--Additional CSS classes applied to the input wrapper div
onChangeReact.ChangeEventHandler--Triggered when the input value changes. Also drives internal search filtering.
...propsReact.HTMLProps<HTMLInputElement>--Additional props to spread to the input element

CommandList

Handles search filtering internally: items are hidden/shown based on the current search query. Groups and separators with no visible items are automatically hidden. The empty state element is toggled based on whether all items are filtered out.

PropTypeDefaultDescription
classNamestring--Additional CSS classes applied to the list
childrenReact.ReactNode--CommandGroup, CommandItem, CommandEmpty, and CommandSeparator elements
...propsReact.HTMLProps<HTMLUListElement>--Additional props to spread to the ul element (role="listbox")

CommandItem

PropTypeDefaultDescription
valuestring''The internal value for selection
disabledbooleanfalseWhether the item is disabled
textValuestring--Optional text used for filtering/typeahead. Defaults to the item's text content.
onSelect(value: string) => void--Called when the item is selected (via Enter, Space, or pointer)
...propsReact.HTMLProps<HTMLLIElement>--Additional props to spread to the li element (role="option")

CommandGroup

PropTypeDefaultDescription
headingReact.ReactNode--Optional heading for the group. Renders a div with data-slot="command-group-heading".
...propsReact.HTMLProps<HTMLDivElement>--Additional props to spread to the group div (role="group")

CommandEmpty

Renders a div with role="status" and aria-live="polite". Hidden by default; the list's filtering logic shows it when all items are filtered out.

PropTypeDefaultDescription
classNamestring--Additional CSS classes applied to the empty state
childrenReact.ReactNode--Content shown when no items match the search query
...propsReact.HTMLProps<HTMLDivElement>--Additional props to spread to the div element

CommandShortcut

PropTypeDefaultDescription
keysstring (optional)--Keyboard shortcut string (e.g. "ctrl+K")
onKeysPressed() => void (optional)--Triggered when shortcut is pressed
variant'default' | 'secondary''default'Visual style variant
...propsReact.HTMLProps<HTMLElement>--Additional props to spread to the kbd element

CommandSeparator

Renders a div with role="separator" and aria-hidden. Visibility is automatically managed by the list's filtering logic.

PropTypeDefaultDescription
classNamestring--Additional CSS classes applied to the separator
...propsReact.HTMLProps<HTMLDivElement>--Additional props to spread to the separator div

CommandDialog

PropTypeDefaultDescription
childrenReact.ReactNode--Command content rendered inside the dialog (typically CommandInput, CommandList, etc.)
...propsReact.ComponentPropsWithRef<typeof Dialog>--Additional props inherited from Dialog.

Types

CommandContextValue

Returned by useCommandContext(). Provides root command state.

PropertyTypeDescription
searchstringCurrent search query value.
onSearchChange(search: string) => voidCallback to update the search query.
dir'ltr' | 'rtl'Text direction for the command tree. Uses the same useDirection resolution order.
listIdstringAuto-generated id linking the input to the list via aria-controls.
inputRefReact.RefObject<HTMLInputElement | null>Search input field reference.
typeaheadSearchRefReact.RefObject<string>Ref tracking the current typeahead search string.

CommandListContextValue

Returned by useCommandListContext(). Provides list-level state.

PropertyTypeDescription
onItemLeave() => voidCalled when pointer leaves an item; returns focus to the input.
listRefReact.RefObject<HTMLUListElement | null>The ul element holding items.
emptyRefReact.RefObject<HTMLDivElement | null>The empty state div ref.
selectedItemHTMLLIElement | nullThe first visible item after filtering.

CommandItemContextValue

Per-item context. Available via useCommandItemContext().

PropertyTypeDescription
valuestringThe item's value.
disabledbooleanWhether the item is disabled.
textIdstringAuto-generated id for the item's text label.
onItemTextChange(node: HTMLElement | null) => voidCallback to update the item's text value from the DOM.

CommandBadgeProps (CommandShortcut)

PropertyTypeDescription
keysstring (optional)Keyboard shortcut string (e.g. 'ctrl+K').
onKeysPressed() => void (optional)Called when the shortcut is triggered.
variant'default' | 'secondary' (optional)Visual style.
...propsReact.HTMLProps<HTMLElement>Native kbd element props.

RTL Support

RTL is handled at the primitive layer. Set dir="rtl" on the root Command for a local override, or set DirectionProvider once at app/root level for global direction. Child primitives inherit direction automatically.

<Command dir="rtl">
  <CommandInput placeholder="..." />
  <CommandList>
    <CommandGroup heading="...">
      <CommandItem>...</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>
<Command dir="rtl">
  <CommandInput placeholder="..." />
  <CommandList>
    <CommandGroup heading="...">
      <CommandItem>...</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>