Build · Components
A command menu with pages

A command menu with pages

Maggie Appleton catalogues the pattern in Command K Bars (November 2022), and her definition is the place to start. “are command-line bars that pop up in the middle of the screen when you hit a certain keyboard shortcut.” They go by command palettes, command launchers, and omniboxes too; the shortcut is traditionally CMD + K, hence the nickname, though CMD + E and CMD + / have been contenders. Her examples are Linear, Todoist, Framer, Tana, Raycast, and the pre-release Lazy, with the lineage traced back to Apple’s Spotlight in MacOS 10.4 Tiger in 2005, summoned with CMD + Space, and to Quicksilver among the independent launchers that followed.

Her argument for the pattern is a trade-off between pixel real estate, cognitive load, and complexity. GUIs make affordances visible and are easy to learn, but “they don’t scale very well to complex systems with hundreds of commands.” Her counter-example is Cinema 4D, which needs a GUI and still drowns in icons and panels. A command bar lets people search every available command, so “rather than remembering which sub-sub-sub menu a function lives in, users need only remember its name,” and fuzzy search means not even that: typing “make” should surface actions about creating things. Stashing rarely used items in the bar clears screen space and reduces what users attend to at once. She notes the bars double as universal search over documents and tasks, and that the pattern is proliferating, with commandbar.com, kbar, and cmdk as ready-made options.

The last of those is Paco Coursey’s . The README says it plainly: “⌘K is a command menu React component that can also be used as an accessible combobox. You render items, it filters and sorts them automatically.” Paco’s home page calls it a “composable command menu React component,” and the history line in the README says he wrote it in 2019 to see if a composable combobox API was possible, that Rauno Freiberg used it for the Vercel command menu and autocomplete in 2020, and that it was rewritten independently in 2022 with a simpler and more performant approach, with ideas and help from Shu. Rauno lists ⌘K (2022) among his own projects and describes it on Devouring Details as software that is “downloaded millions of times per week to power command menu interfaces for the most modern productivity apps on the web.”

The API is a set of parts, each forwarding props and ref and each carrying a cmdk- data attribute for styling: Command, Dialog (which composes Radix UI’s Dialog and always renders its overlay), Input, List, Item, Group, Separator, Empty, and Loading. Items take a value, inferred from textContent if you omit it, and become active on pointer enter. Groups never unmount; they get the hidden attribute. Empty renders itself when nothing matches. List exposes a --cmdk-list-height variable so you can transition the height. You can pass a custom filter function, give items keywords that act as aliases and affect rank, turn filtering and sorting off entirely by setting shouldFilter to false, and make the arrow keys wrap with the loop prop. The FAQ is a list of firm answers: accessible, yes, tested with VoiceOver; virtualization, no, but good performance up to 2,000 to 3,000 items; listen for ⌘K automatically, no, “do it yourself to have full control over keybind context.”

The ARCHITECTURE document explains the cost of that composability. The whole library “is born from a simple constraint: can you write a combobox with filtering and sorting using the compound component approach?” Compound components are natural to write, and Paco notes Radix UI shipped with the same structure shortly after. But for a combobox he calls it “a terrible, terrible constraint that we’ve spent 2 years fighting.” Every item and group stays rendered in the React tree and adds or removes itself from the DOM based on the search; “the DOM is authoritative,” so selection order follows DOM order, which follows render order, which you provide. He rejected React.Children iteration because it cannot peek inside a composed component, rejected object arrays of items because their interface grows with edge cases, and rejected render props as inelegant. Selection was once tracked by index and is now tracked by value, because under Strict Mode effects run twice and an index is not stable. The price is memory: with 2,000 items filtered to 2, you still allocate 2,000 Item instances, though the DOM shrinks to 2. His craft page for the original Command Menu (2021) recalls a testing set of 249 items and a later goal of 2,000 at reasonable performance.

Depth comes from pages. The README’s example is the canonical one: “selecting ‘Change theme…’ should show new items ‘Dark theme’ and ‘Light theme’. We call these sets of items ‘pages’, and they can be implemented with simple state,” with Escape or Backspace on an empty search popping back out. Maggie describes the same move in Tana, where “each selection in the bar moves you onto the next set of available choices.” Rauno’s craft index has two tiles on this exact thing: Command Menu (November 2021) and ⌘K Breadcrumbs (December 2022), the breadcrumbs being the trail that tells you which page you are on. The figure below is a small command menu with two pages. Type to filter, use the arrows and Enter, and notice that items ending in an ellipsis open a page, that the breadcrumb row changes, and that Backspace on an empty input takes you back.

to navigate to select to go back

Figure 2. A command menu with pages. Type to filter, or pick an item with the arrow keys and Enter. Items ending in an ellipsis open a nested page; the breadcrumbs show where you are, and Backspace on an empty input or Escape steps back out. The list keeps a fixed height so the card never jumps between pages.Illustrative numbers

One thing the figure does not do is animate its opening, and that is a rule rather than an omission. In Invisible Details of Interaction Design Rauno uses command menus as his example of when not to animate: “It’s tempting to throw an opacity and scale fade on the overlay. But if we for a moment consider the interaction frequency being hundreds of times a day, it does start to feel more like cognitive burden after seeing the same animation for the hundredth time.” Keyboard input feels more mechanical than touch, and the novelty of a thing you do constantly is low. Emil Kowalski agrees in his skill file, whose frequency table puts the command palette toggle in the 100-plus-times-a-day row with the verdict “No animation. Ever.” Frequency and novelty goes through the reasoning in full.

What a command menu should do instead is be fast, be keyboard-first, and be honest about where you are. Filter as you type, keep the selection stable when the list changes, let the arrows wrap if you want them to, and treat Escape and Backspace as the way back up a page. Put the keyboard shortcut next to the action, as Maggie notes Todoist does. And if you reach for a library, notice what cmdk refuses to decide for you: the keybinding, the styling, and the items themselves. Those are yours.