A headless React hook
Command Menu
Build your own command menu
Headless, keyboard-first, fully typed
- HomeGo to homepage
- DocumentationRead the docs⌘D
- AboutLearn more about us
- ContactGet in touch
- Open FileOpen a project file⌘O
- SearchSearch the project⌘F
- Copy LinkCopy current URL⌘K
- Open TerminalLaunch terminal⌘J
- SettingsOpen preferences⌘⇧S
Features
All of the behaviour, none of the markup.
- 01
Headless
Every element is yours to render. The hook hands back props and state, never markup or styles.
- 02
Keyboard first
Arrow keys skip disabled items, Enter selects, and ⌘ or Ctrl shortcuts fire from anywhere in the menu.
- 03
Accessible
Combobox, listbox and option roles come wired in, with the highlighted option announced as you move.
- 04
Groups and async
Sort items into sections and stream remote results in as their own group, all in one keyboard order.
- 05
Fully typed
Extra fields on your items come back typed, and group ids are checked against your config.
- 06
Light
No dependencies beyond React. Moving the highlight re-renders only the two rows it leaves and lands on.
Usage
One hook, your markup.
Hand the hook your items. It gives back the filtered list and a few sets of props to spread onto your own elements.
-
menuPropsthe wrapper, for the keyboard -
searchPropsthe input, as a combobox -
listPropsthe list, as its listbox -
itemPropseach row, as an option
import { type Config, useCommandMenu } from "commandmenu";
const config = [
{ id: "docs", label: "Documentation", onSelect: openDocs },
{ id: "search", label: "Search", shortcut: "F", onSelect: openSearch },
] as const satisfies readonly Config[];
export const CommandMenu = () => {
const { menuProps, searchProps, listProps, list, selectedId } =
useCommandMenu({ config });
return (
<div {...menuProps}>
<input {...searchProps} placeholder="Search…" />
<ul {...listProps}>
{list.map((item) => (
<li
key={item.id}
{...item.itemProps}
aria-selected={item.id === selectedId}
>
{item.label}
</li>
))}
</ul>
</div>
);
};