Skip to content
Nizivo UI

CommandMenu

Unstable: a searchable list of places and actions in a dialog, opened with ⌘K (Ctrl+K elsewhere) or a button, as Figma's Command Menu draws it. Import it from @nizivo/react/unstable/command-menu.

Tier: ownUnstable

Import

import { CommandMenu } from '@nizivo/react/unstable/command-menu';

// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/dialog.css and @nizivo/react/css/kbd.css
import '@nizivo/react/css/command-menu.css';

Unstable

It ships at @nizivo/react/unstable/command-menu, outside the semver promise. Still to come: results that load as the reader types (async sources), a cap per group, a long list that renders only what shows, a spoken count of the results, and loading the menu only on the first ⌘K.

Static or interactive

Client. It is the Dialog with React Aria's Autocomplete inside. The server renders the trigger; the dialog renders only after hydration, when it opens. How to choose.

Parts

CommandMenu.Root holds whether it is open and registers its shortcut (hotkey, 'mod+k' by default; null for none). CommandMenu.Trigger opens it from the pointer and touch, and names the shortcut through aria-keyshortcuts. CommandMenu.Popup is the dialog; in it, CommandMenu.Input (the search row, with an "esc" hint), CommandMenu.List of CommandMenu.Groups with a heading and their CommandMenu.Items, and CommandMenu.Footer, the keys as Figma shows them.

Rows

An item is a link (href) or an action (onAction, in a client component); choosing it closes the menu. icon draws a 20 px mark before its text, kind says what it is at the row's end ("Product", "Order"), and shortcut draws its own Kbd and announces it. The search matches textValue, or the item's text when it is a string. filter on the Popup is 'contains' (default) or 'startsWith'.

Shortcuts

The menu's shortcut is registered in @nizivo/react/hotkeys, one list for the whole page that a help dialog can read with useHotkeys. useHotkey and registerHotkey add your own: they never fire during an IME composition or while the reader types in a field, unless allowInInputs, and a single printable key with no modifier warns in development (WCAG 2.1.4).

Examples

Basic

Figma's command menu: products, an order and actions, filtered as you type. Press ⌘K (Ctrl+K elsewhere) or the button; the arrow keys move, Enter opens the row, Escape clears the search and then closes.

import type { JSX } from 'react';
import { Button } from '@nizivo/react/button';
import { CommandMenu } from '@nizivo/react/unstable/command-menu';

/** Stand-ins for `@nizivo/icons` glyphs. */
function Box(): JSX.Element {
  return (
    <svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor">
      <path d="M4 7l8-4 8 4v10l-8 4-8-4z" />
    </svg>
  );
}

function Sheet(): JSX.Element {
  return (
    <svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor">
      <path d="M6 3h12v18H6zM9 8h6M9 12h6" />
    </svg>
  );
}

function Plus(): JSX.Element {
  return (
    <svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor">
      <path d="M12 5v14M5 12h14" />
    </svg>
  );
}

export function CommandMenuExample(): JSX.Element {
  return (
    <CommandMenu.Root>
      <CommandMenu.Trigger render={<Button variant="secondary">Search…</Button>} />
      <CommandMenu.Popup>
        <CommandMenu.Input placeholder="Search products, orders and actions" />
        <CommandMenu.List>
          <CommandMenu.Group heading="Products">
            <CommandMenu.Item href="#stoneware-mug" icon={<Box />} kind="Product">
              Stoneware mug
            </CommandMenu.Item>
            <CommandMenu.Item href="#stoneware-bowls" icon={<Box />} kind="Product">
              Stoneware bowl set
            </CommandMenu.Item>
          </CommandMenu.Group>
          <CommandMenu.Group heading="Orders">
            <CommandMenu.Item href="#order-1042" icon={<Sheet />} kind="Order">
              #1042 · Stoneware mug ×2
            </CommandMenu.Item>
          </CommandMenu.Group>
          <CommandMenu.Group heading="Actions">
            <CommandMenu.Item href="#new-product" icon={<Plus />} kind="Action" shortcut="mod+n">
              Create product
            </CommandMenu.Item>
            <CommandMenu.Item
              href="#new-discount"
              icon={<Plus />}
              kind="Action"
              shortcut="mod+shift+d"
            >
              Create discount
            </CommandMenu.Item>
          </CommandMenu.Group>
        </CommandMenu.List>
        <CommandMenu.Footer />
      </CommandMenu.Popup>
    </CommandMenu.Root>
  );
}

Parts

Use the parts through the namespace (CommandMenu.Root) or by their flat names (CommandMenuRoot); both work from Server Components.

CommandMenu.Root

Client component Renders no element of its own. Props: CommandMenuRootProps. Source: src/unstable/command-menu/command-menu.client.tsx.

NameTypeDefaultDescription
childrenrequiredReactNode–
defaultOpenboolean–
hotkeystring | null'mod+k'

The shortcut that opens and closes it, as Kbd writes it; null for none.

labelsPartial<CommandMenuLabels>–
onOpenChange(open: boolean) => void–
openboolean–

CommandMenu.Trigger

Client component Renders no element of its own. Props: CommandMenuTriggerProps. Source: src/unstable/command-menu/command-menu.client.tsx.

NameTypeDefaultDescription
childrenReactNode–
renderRenderProp<DialogTriggerState>–

Must render a <button>, for example a field-looking Button in a top bar.

  • Also takes every attribute of <button> except children. (Omit<ComponentPropsWithRef<"button">, "children">)

render takes an element, or, in client code, a function of the props and this state (DialogTriggerState):

NameTypeDefaultDescription
disabledrequiredboolean–
focusVisiblerequiredboolean–
openrequiredboolean–
pressedrequiredboolean–

CommandMenu.Popup

Client component Renders no element of its own. Props: CommandMenuPopupProps. Source: src/unstable/command-menu/command-menu.client.tsx.

NameTypeDefaultDescription
childrenrequiredReactNode–

CommandMenu.Input, CommandMenu.List and CommandMenu.Footer.

classNamestring–
filterCommandMenuFilter
"contains""startsWith"
'contains'

CommandMenu.Input

Client component Renders data-slot="command-menu-input" with the class nz-command-menu-input. Props: CommandMenuInputProps. Source: src/unstable/command-menu/command-menu.client.tsx.

NameTypeDefaultDescription
classNamestring–
placeholderstring–

CommandMenu.List

Client component Renders data-slot="command-menu-list" with the class nz-command-menu-list. Props: CommandMenuListProps. Source: src/unstable/command-menu/command-menu.client.tsx.

NameTypeDefaultDescription
childrenrequiredReactNode–
classNamestring–
emptyReactNode–

Replaces labels.empty when nothing matches.

CommandMenu.Group

Client component Renders data-slot="command-menu-group" with the class nz-command-menu-group. Props: CommandMenuGroupProps. Source: src/unstable/command-menu/command-menu.client.tsx.

NameTypeDefaultDescription
childrenrequiredReactNode–
classNamestring–
headingrequiredReactNode–

The group's heading, in Select's group label look.

CommandMenu.Item

Client component Renders data-slot="command-menu-item" with the class nz-command-menu-item. Props: CommandMenuItemProps. Source: src/unstable/command-menu/command-menu.client.tsx.

NameTypeDefaultDescription
childrenrequiredReactNode–
classNamestring–
disabledboolean–
hrefstring–

A link: choosing the row follows it.

iconReactNode–

A 20 px mark before the text. Decorative.

kindReactNode–

What it is ("Product", "Order", "Action"), at the row's end.

onAction() => void–

Runs when the row is chosen (client components only).

shortcutstring–

Its own shortcut, drawn as a Kbd ("mod+n") and announced through aria-keyshortcuts.

textValuestring–

The text the search matches; defaults to children when it is a string.

The keys the menu takes, as Figma's footer shows them. Decorative: the roles say the same.

Client component Renders no element of its own. Props: CommandMenuFooterProps. Source: src/unstable/command-menu/command-menu.client.tsx.

NameTypeDefaultDescription
classNamestring–

Other exports

CommandMenuFilter type

import type { CommandMenuFilter } from '@nizivo/react/unstable/command-menu'

How the typed text matches an item's text: anywhere in it, or at its start; case and accents ignored.

type CommandMenuFilter = "contains" | "startsWith"

CommandMenuFooterProps type

import type { CommandMenuFooterProps } from '@nizivo/react/unstable/command-menu'

The props of CommandMenu.Footer, listed above.

CommandMenuGroupProps type

import type { CommandMenuGroupProps } from '@nizivo/react/unstable/command-menu'

The props of CommandMenu.Group, listed above.

CommandMenuInputProps type

import type { CommandMenuInputProps } from '@nizivo/react/unstable/command-menu'

The props of CommandMenu.Input, listed above.

CommandMenuItemProps type

import type { CommandMenuItemProps } from '@nizivo/react/unstable/command-menu'

The props of CommandMenu.Item, listed above.

CommandMenuItemState type

import type { CommandMenuItemState } from '@nizivo/react/unstable/command-menu'

NameTypeDefaultDescription
disabledrequiredboolean–
focusVisiblerequiredboolean–
highlightedrequiredboolean–

The active row: the arrow keys or the pointer are on it.

CommandMenuLabels type

import type { CommandMenuLabels } from '@nizivo/react/unstable/command-menu'

CommandMenu's own strings (§16.4): serialisable, English defaults.

NameTypeDefaultDescription
emptyrequiredstring'No results'

Shown when nothing matches.

labelrequiredstring'Command menu'

The dialog's and the search field's name.

navigaterequiredstring'Navigate'

The footer's arrow keys hint.

openrequiredstring'Open'

The footer's Enter hint.

placeholderrequiredstring'Search'

The search field's placeholder.

CommandMenuListProps type

import type { CommandMenuListProps } from '@nizivo/react/unstable/command-menu'

The props of CommandMenu.List, listed above.

CommandMenuPopupProps type

import type { CommandMenuPopupProps } from '@nizivo/react/unstable/command-menu'

The props of CommandMenu.Popup, listed above.

CommandMenuRootProps type

import type { CommandMenuRootProps } from '@nizivo/react/unstable/command-menu'

The props of CommandMenu.Root, listed above.

CommandMenuTriggerProps type

import type { CommandMenuTriggerProps } from '@nizivo/react/unstable/command-menu'

The props of CommandMenu.Trigger, listed above.

Data attributes and slots

Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.

SlotRendered byData attributes
command-menu-group
.nz-command-menu-group
CommandMenu.Group
root (no render)
none
command-menu-group-label
.nz-command-menu-group-label
CommandMenu.Group
inner element (CSS only)
none
command-menu-input
.nz-command-menu-input
CommandMenu.Input
root (no render); interactive, focus: self
none
command-menu-item
.nz-command-menu-item
CommandMenu.Item
root (no render); interactive, focus: engine
  • data-disabled (present or absent) ← disabled
    Mirrors the disabled attribute
  • data-focus-inset (present or absent)
    a11y-floor opt-out: draw the focus outline inside the part (outline-offset -2px), for clipped scrollers such as DialogBody and table cells (§8.5)
  • data-focus-visible (present or absent)
    Focus is visible; elsewhere use :focus-visible
  • data-highlighted (present or absent)
    Keyboard or virtual focus, or pointer hover
command-menu-list
.nz-command-menu-list
CommandMenu.List
root (no render)
none
command-menu-trigger
.nz-command-menu-trigger
CommandMenu.Trigger
inner element (CSS only); interactive, focus: self
none

CSS hooks

Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.

HookDefaultRead as
--nz-command-menu-bg--nz-bg-surface
  • background on .nz-dialog-popup.nz-command-menu-popup
--nz-command-menu-item-bg-active--nz-bg-hover
  • background on .nz-command-menu-item[data-highlighted]

Layers: nz.components. Classes: .nz-command-menu-empty, .nz-command-menu-field, .nz-command-menu-footer, .nz-command-menu-group, .nz-command-menu-group-label, .nz-command-menu-hint, .nz-command-menu-icon, .nz-command-menu-input, .nz-command-menu-item, .nz-command-menu-kind, .nz-command-menu-label, .nz-command-menu-list, .nz-command-menu-popup, .nz-command-menu-search, .nz-dialog-popup.

Tokens this stylesheet reads (29)

--nz-bg-hover, --nz-bg-subtle, --nz-bg-surface, --nz-border-subtle, --nz-border-width-default, --nz-font-body-l, --nz-font-body-m, --nz-font-label-m, --nz-font-label-s, --nz-icon-secondary, --nz-radius-sm, --nz-radius-xl, --nz-size-icon-md, --nz-space-0-5, --nz-space-1, --nz-space-1-5, --nz-space-2, --nz-space-3, --nz-space-4, --nz-space-5, --nz-space-6, --nz-text-disabled, --nz-text-primary, --nz-text-secondary, --nz-text-tertiary, --nz-tracking-body-l, --nz-tracking-body-m, --nz-tracking-label-m, --nz-tracking-label-s

Accessibility

These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:

  • command-menu-input: draws the focus outline itself on keyboard focus.
  • command-menu-item: draws the focus outline when the engine reports keyboard focus, not on pointer hover.
  • command-menu-trigger: draws the focus outline itself on keyboard focus.

Keyboard

KeysAction
⌘K, Ctrl+KOpens the menu, also from a text field; again, closes it.
TypingFilters the rows, case and accents ignored.
Arrow Down, Arrow UpMove the active row; focus stays in the search field.
EnterRuns the active row and closes the menu.
EscapeClears the search first; on an empty search, closes the menu and returns focus.

Source: React Aria Components 1.22.1 Autocomplete, SearchField and Menu (_engines/react-aria/command-menu.client.tsx) inside the Dialog; the shortcut from @nizivo/react/hotkeys. The browser lane (test/browser/command-menu.spec.tsx) checks ⌘K, the trigger, filtering, the arrow keys, Enter, Escape and the hotkeys policy.

Figma

Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.

On this page