@lp-labs/react-command-menu
v0.1.0
Published
Searchable accessible React command palettes with keyboard navigation.
Readme
@lp-labs/react-command-menu
Searchable command palettes with keyboard navigation, async actions and optional modal focus management. React 18+ peer only; no runtime dependencies or CSS imports. Includes TypeScript, ESM, CommonJS and browser entries.
Installation
npm install @lp-labs/react-command-menuRequires React 18 or later in the host application and a modern browser for focus and keyboard handling. Server-side rendering is supported. Node.js consumers require Node.js 18 or later.
Quick Start
import { useState } from 'react';
import CommandMenu, { useCommandMenu } from '@lp-labs/react-command-menu';
export default function Example() {
const menu = useCommandMenu();
const [selection, setSelection] = useState('None');
return (
<>
<button onClick={menu.toggle}>Commands</button>
<output aria-live="polite">Selected: {selection}</output>
<CommandMenu
{...menu.menuProps}
shortcut="mod+k"
commands={[
{ id: 'save', label: 'Save document', keywords: ['write'], onSelect: () => setSelection('Save') },
{ id: 'settings', label: 'Open settings', onSelect: () => setSelection('Settings') },
]}
/>
</>
);
}Replace the example callbacks with your application actions. mod+k means Ctrl+K or Cmd+K; command-level shortcut labels do not register additional keyboard handlers.
Command Data
Commands require unique id and label; optional keywords, group (search metadata), disabled, shortcut (display only), and async onSelect(command). Global onSelect(command) runs after the command action. Errors keep the menu open and render an alert; duplicate executions are blocked while an action is pending.
CommandMenu API
| Prop | Default | Description |
| --- | --- | --- |
| commands | [] | Available command objects |
| open / defaultOpen | false | Controlled or initial visibility |
| onOpenChange(open) | none | Requested visibility change |
| query / defaultQuery | '' | Controlled or initial search text |
| onQueryChange(query) | none | Search-text changes |
| onSelect(command) | none | Runs after the selected command's action |
| modal | true | Display an overlay and keep Tab on search |
| closeOnSelect | true | Close after successful execution |
| shortcut | none | Optional global shortcut such as 'mod+k' |
| label | 'Commands' | Accessible menu and search label |
| placeholder | 'Search commands' | Search placeholder |
| emptyContent | 'No commands' | Empty-result content |
| renderCommand(command, { active }) | command label | Custom result content |
className and style customize the panel. Controlled values require applying callbacks. Search matches every space-separated term across labels, keywords and groups; filterCommands(commands, query) is exported. useCommandMenu(defaultOpen?) returns open, setOpen, toggle, and menuProps for the component.
Keyboard And Focus
| Key | Action | | --- | --- | | Up / Down | Move through enabled results, wrapping at the ends | | Home / End | First / last enabled result | | Enter | Execute the active result | | Escape | Close the menu | | Tab | Stay on search in modal mode; normal navigation in inline mode |
Compatibility And Limits
Arrow keys wrap and skip disabled commands; Home/End jump, Enter executes, Escape closes. Modal mode focuses search, keeps Tab there, and restores the previous focus on closing; results use combobox/listbox semantics. Custom result content should be presentational, not additional tab stops. The modal is rendered in place rather than through a portal; parent clipping/transforms can affect it. Background content is not made inert and document scrolling is not locked. Inline mode (modal={false}) does not trap Tab. Global shortcuts are opt-in and should be owned by only one mounted palette. Async actions cannot be canceled automatically.
There is no fuzzy-ranking engine, nested command-page stack, automatic background inertness, or automatic action cancellation. Group names are search metadata, not visual section headings.
Support
Support maintenance with a PayPal donation.
Author
Created by Libin Prasanth.
