Command menus

The search-first modal a keystroke opens. One compound CommandMenu owning the dialog, filter, selection, and shortcuts; the items and actions are yours.

Demo

Pick a composition, open it, copy the result. Composition decides which slots the root gets — the snippet is a tree.

Composition
import { CommandMenu } from "@/components/application/command-menus/command-menu"; <CommandMenu isOpen={isOpen} onOpenChange={setIsOpen} items={groups} emptyState={<NoResults />} > <CommandMenu.Group> <CommandMenu.List> {(group) => ( <CommandMenu.Section {...group}> {(item) => <CommandMenu.Item key={item.id} {...item} />} </CommandMenu.Section> )} </CommandMenu.List> </CommandMenu.Group> </CommandMenu>

The preview opens a real modal

Every demo on this page mounts the actual dialog over the whole viewport. Press Escape, or click the backdrop, to get back to the page.

Contextual demo

A command menu is a modal over a product, so half of what it looks like is what it covers. It gets a whole screen.

Opening it is yours to wire

The root takes isOpen and draws a ⌘/ chip that focuses the field. It does not bind ⌘K to open itself. The preview adds that binding the way an application would.

The family

Fourteen exports across seven files, two of them React Aria re-exports. The root is the only one you construct.

The components in this family, with their descriptions and import paths
ComponentDescriptionImport from
CommandMenuThe root. Owns the dialog, the search field, the filter, the selection, and the hotkey registrations. Everything else is a slot inside it.@/​components/​application/​command-menus/​command-menu
CommandMenu.ListThe listbox. Reads the root’s items from context, renders the empty state, and is the only thing that scrolls.@/​components/​application/​command-menus/​command-menu
CommandMenu.SectionOne group of items, with an optional title and a rule under it. The last section keeps the rule and paints it transparent.@/​components/​application/​command-menus/​command-menu
CommandMenu.ItemOne row: an optional leading icon, avatar, or dot, a label, an optional description, and an optional shortcut chip.@/​components/​application/​command-menus/​base-components/​command-menu-item
CommandMenu.GroupThe wrapper that puts the list beside a preview pane. It is a bare min-h-0 flex-1 div, so the row layout is a flex class you add, as every kit example with a pane does.@/​components/​application/​command-menus/​command-menu
CommandMenu.PreviewRender-props access to the selected item’s id, for the detail pane beside the list. Renders nothing while nothing is selected.@/​components/​application/​command-menus/​command-menu
CommandMenu.FooterThe keyboard legend pinned to the bottom of the dialog. Hidden below 768px.@/​components/​application/​command-menus/​base-components/​command-menu-footer
CommandMenu.TriggerReact Aria’s DialogTrigger, re-exported. Wrap a button in it instead of holding open state yourself.@/​components/​application/​command-menus/​command-menu
CommandMenu.CollectionReact Aria’s Collection, re-exported, for feeding a section its items without a wrapper element.@/​components/​application/​command-menus/​command-menu
CommandDialogThe overlay and the modal shell. Exported on its own, though the root always renders one for you.@/​components/​application/​command-menus/​command-menu
CommandInputThe search field: magnifier, input, optional help tooltip, optional shortcut chip. The root renders it and passes no tooltip.@/​components/​application/​command-menus/​base-components/​command-input
CommandShortcutThe key chips. Renders one chip per key, joined by an arrow, and uppercases whatever you pass.@/​components/​application/​command-menus/​base-components/​command-shortcut
CommandMenuHeaderThe section title. Rendered for you by CommandMenu.Section when it has a title.@/​components/​application/​command-menus/​base-components/​command-menu-header
CommandMenuNavigationIconOne key cap in the footer legend, as either an icon or a short text label.@/​components/​application/​command-menus/​base-components/​command-menu-navigation-icon

Built from

Eight kit pieces make up the menu; four have their own docs page.

Component
Where it’s used here
AvatarThe avatar row’s portrait, at xs normally and md while stacked.
FeaturedIconThe stacked icon row’s 40px gray modern tile.
ButtonThe footer’s settings control.
TooltipThe search field’s optional help icon, which the root never asks for.

The item

The leaf of the family, and the only piece with its own variant space: four leading types, two text layouts, all live.

Text default
Olivia Rhye@olivia
⌘C
type default
Olivia Rhye@olivia
⌘C
type icon
Olivia Rhye
Olivia Rhye@olivia
⌘C
type avatar
Olivia Rhye@olivia
⌘C
type dot
Text stacked
Olivia Rhye@olivia
⌘C
type default
Olivia Rhye@olivia
⌘C
type icon
Olivia Rhye
Olivia Rhye@olivia
⌘C
type avatar
Olivia Rhye@olivia
⌘C
type dot

An item needs a listbox around it

CommandMenu.Item is a React Aria ListBoxItem, so it only renders inside a collection. The cells above sit in a plain ListBox because CommandMenu.List reads the root’s context and cannot be used outside it.

The compositions

All ten command bars the sheet draws: six types, four drawn again with stacked rows. Each button opens the real dialog.

Command only, text default
Actions, text default
Users, text default
Users menu, text default
Integrations menu, text default
Empty state, text default
Actions, text stacked
Users, text stacked
Users menu, text stacked
Integrations menu, text stacked

The kit ships nine of the ten as example files

Command only is the exception: the sheet draws it, and the code has no file for it, though a root with no List child produces exactly that.

States

The dialog has one state, open. Everything that responds to a user is in the rows and the field.

Olivia Rhye
Olivia Rhye@olivia
⌘C
Olivia Rhye
Olivia Rhye@olivia
⌘C
Selected: bg-primary_hover, seeded here by defaultSelectedKeys
Olivia Rhye@olivia
⌘C
Olivia Rhye@olivia
⌘C
Hover and focus: point at these rows, or tab in and arrow down

Hover and selection are painted the same

Both use bg-primary_hover, and the two computed backgrounds measured identical in both themes, so a row the pointer is over and a row that is actually selected cannot be told apart. The Figma item has no selected state at all; see the divergence table on the Code tab.