Skip to content
WANCRAFT

D.15Navigation & Menus

Command Menu

A searchable command palette with grouped commands, fuzzy matching and full keyboard control.

ReactCSSTailwindadvanced

Overview

Command Menu is a dependency-free command palette. Commands are grouped, filtered with a forgiving fuzzy matcher that prefers word starts and contiguous runs, and the matched characters are highlighted. Arrow keys move through results (skipping disabled ones), Enter runs, and Escape first clears the query, then closes. The dialog variant is built on a native modal <dialog>, so it renders in the top layer, makes the page inert and returns focus; the inline variant renders the same palette in place. A ⌘/Ctrl hotkey toggles it.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/command-menu.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/command-menu.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/command-menu.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
1 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { CommandMenu, type CommandGroup } from "@/components/ui/command-menu";
import { useState } from "react";

export function CommandMenuExample() {
  const [open, setOpen] = useState(false);
  const groups: CommandGroup[] = [
    {
      heading: "Suggestions",
      items: [
        { id: "new", label: "New project", shortcut: ["⌘", "N"], onSelect: () => console.log("new") },
        { id: "docs", label: "Search documentation", keywords: ["help"] },
      ],
    },
    {
      heading: "Navigation",
      items: [{ id: "settings", label: "Open settings", shortcut: ["⌘", ","] }],
    },
  ];

  return (
    <CommandMenu groups={groups} open={open} onOpenChange={setOpen} />
  );
}

03Props

PropTypeDefaultDescription
groupsCommandGroup[]—{ heading: string; items: CommandItem[] }. CommandItem: { id, label, description?, icon?, shortcut?: string[], keywords?: string[], disabled?, onSelect? }.
variant"dialog" | "inline""dialog"Modal palette, or the same palette rendered in place.
openboolean—Open state (controlled, dialog variant).
defaultOpenbooleanfalseInitial open state (uncontrolled).
onOpenChange(open: boolean) => void—Called when the dialog opens or closes.
onSelect(item: CommandItem) => void—Called after any command runs.
hotkeystring"k"Key pressed with ⌘ / Ctrl to toggle the dialog, or focus the inline input.
disableHotkeybooleanfalseDo not register the global hotkey listener.
placeholderstring"Type a command or search…"Input placeholder.
emptyMessagestring"No matching commands."Shown when nothing matches.
labelstring"Command menu"Accessible name of the input, listbox and dialog.
accentstring"#FF7448"Highlight, active-row marker and focus colour.
highlightMatchesbooleantrueHighlight matched characters in labels.
closeOnSelectbooleantrueClose the dialog after a command runs.
showShortcutsbooleantrueShow shortcut hints on each row.
maxListHeightnumber320Maximum height of the results list in px.
classNamestring—Classes added to the palette panel.

04Features

  • 01Grouped commands with fuzzy matching (word starts and contiguous runs score higher)
  • 02Match highlighting, keyword aliases, descriptions, icons and shortcut hints
  • 03Arrow keys skip disabled commands and wrap; Enter runs; Escape clears, then closes
  • 04Native modal <dialog>: top layer, inert background, overlay click to close, focus return
  • 05Inline variant, controlled or uncontrolled open state, configurable ⌘/Ctrl hotkey
motion · tweeninput · keyboardinput · clickinput · focus

05Accessibility

  • ARIA combobox pattern: the input owns a listbox via aria-controls and tracks the active option with aria-activedescendant, so focus never leaves the input.
  • Options use role="option" with aria-selected / aria-disabled inside labelled groups.
  • The result count is announced through a polite live region.
  • The dialog is modal (showModal), traps focus by making the page inert, and returns focus to the trigger on close.

06Limitations

  • The dialog does not lock page scrolling while open.
  • Shortcut hints are display-only; wire real shortcuts in your app.