CommandMenu
ImplementedPortal command palette with keyboard navigation and shortcut registration. Real implementation; the organisms/commandmenu stories twin was removed per ADR-035.
components/molecules/CommandMenu/CommandMenu.tsxCommand palette with search.
CommandMenu
Feedback & Overlay — Family 4
Command palette with search.
Usage
CommandMenu is a controlled component — pair open with a piece of local state and wire onOpenChange so Escape and the backdrop dismiss it. Keyboard navigation (arrows, Enter, Escape) and focus management are built in.
import { CommandMenu, Button } from '@epasslive/design-system'
const [open, setOpen] = useState(false)
<Button variant="primary" onClick={() => setOpen(true)}>Open</Button>
<CommandMenu
open={open}
onOpenChange={setOpen}
groups={[
{
id: 'navigation',
label: 'Navigation',
items: [{ id: 'home', label: 'Go to home', onSelect: () => setOpen(false) }],
},
]}
/>i18n and focus hooks
Localize the palette chrome with labels and the filter input with inputAriaLabel / inputPlaceholder. Use onFocusTrap / onRestoreFocus to save and restore the trigger element’s focus across the open/close cycle (e.g. when opening from a global ⌘K shortcut with no trigger button).
const triggerRef = useRef<HTMLButtonElement | null>(null)
<CommandMenu
open={open}
onOpenChange={setOpen}
onFocusTrap={() => { triggerRef.current = document.activeElement as HTMLButtonElement | null }}
onRestoreFocus={() => { triggerRef.current?.focus() }}
labels={{ openMenu: 'Menu de commandes', noResults: 'Aucun résultat' }}
inputAriaLabel="Rechercher une commande"
inputPlaceholder="Tapez une commande…"
groups={groups}
/>When to use CommandMenu vs SearchSelect vs Menu
Use CommandMenu for global, power-user actions — a ⌘K-style palette aggregating navigation shortcuts and commands across the whole app, opened from anywhere. Use SearchSelect when the user must pick one option within a form from a searchable list (its value feeds form state). Use Menu for short, contextual action lists anchored to a trigger in the page. Rule of thumb: form field → SearchSelect; anchored context actions → Menu; global palette → CommandMenu.
Props
open*booleanControls visibility of the palette.
onOpenChange*(open: boolean) => voidCalled on Escape, backdrop click, or after selecting an item.
groups*CommandMenuGroup[]Grouped list of commands rendered in the palette.
placeholderstring'Type a command…'Search-input placeholder text.
inputPlaceholderstringExplicit placeholder alias for the filter input (falls back to `placeholder`).
emptyMessagestring'No results'Rendered when the filtered list is empty.
labelsCommandMenuLabelsLocalizable chrome strings — openMenu (dialog name), noResults, closeLabel (backdrop button), listLabel (listbox).
inputAriaLabelstring'Search commands'Accessible name for the filter input (aria-label).
onFocusTrap() => voidFired when the dialog opens — save document.activeElement before focus moves to the input.
onRestoreFocus() => voidFired when the dialog closes — restore the saved focus target.
idstringStable id prefix for the dialog and list elements (defaults to `cmd-<useId>`).
Props
openboolean; onOpenChange: (open: boolean) => void; groups: CommandMenuGroup[]—Whether the element is open/expanded.
placeholderstring; emptyMessage?: string; id?: string'Type a command…'Placeholder text shown when the value is empty.
inputAriaLabelstring'Search commands'Accessible name for the filter input (§126 Wave 3 #6) — previously hardcoded.
inputPlaceholderstring(placeholder)Placeholder for the filter input — explicit alias of placeholder.
onFocusTrap() => void—Fired when the dialog opens and focus moves into the input — wrapper hook for saving document.activeElement.
onRestoreFocus() => void—Fired when the dialog closes — wrapper hook for restoring saved focus. Tab is trapped inside the dialog while open.
labelsCommandMenuLabels—Localizable chrome strings — dialog name (openMenu) + noResults (D1 merge 2 — CommandMenu replacement).
<CommandMenu />Usage
Import
import { CommandMenu } from "@epasslive/design-system"Example 1
<CommandMenu />Anti-patterns
— Do not compose atoms directly — use molecules instead
— Avoid inline styles on atoms