Skip to main content

Command Menu

Command Menu is a searchable overlay for running commands and navigating, like jumping to a page or switching projects. It opens with a keyboard shortcut and filters grouped items as people type.

Import

import { CommandMenu } from '@vortexlabs/vortex';
import type { CommandMenuProps } from '@vortexlabs/vortex';

Other links

"use client";

import { Button, CommandMenu } from "@vortexlabs/vortex";
import { Gear, PlusLarge, Search, UserPlus } from "@vortexlabs/vortex-icons";
import { useState } from "react";

export const WorkspaceCommands = () => {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open command menu</Button>
      <CommandMenu.Root open={open} onOpenChange={setOpen}>
        <CommandMenu.Content placeholder="Type a command or search...">
          <CommandMenu.Group label="Projects">
            <CommandMenu.Item icon={Search} onSelect={() => setOpen(false)}>
              Search projects
            </CommandMenu.Item>
            <CommandMenu.Item
              icon={PlusLarge}
              shortcut={["N"]}
              onSelect={() => setOpen(false)}
            >
              New project
            </CommandMenu.Item>
          </CommandMenu.Group>
          <CommandMenu.Group label="Workspace">
            <CommandMenu.Item
              icon={UserPlus}
              shortcut={["I"]}
              onSelect={() => setOpen(false)}
            >
              Invite member
            </CommandMenu.Item>
            <CommandMenu.Item
              icon={Gear}
              shortcut={["⌘", ","]}
              onSelect={() => setOpen(false)}
            >
              Settings
            </CommandMenu.Item>
          </CommandMenu.Group>
        </CommandMenu.Content>
      </CommandMenu.Root>
    </>
  );
};

Features

  • Opens with a keyboard shortcut.
  • Filters grouped commands by label and keywords.
  • Shows icons and shortcut labels on items.
  • Supports links, nested menus, and loading states.

Structure

Root holds the open state, Content renders the search and results, Group labels a section, and Item is one command. Items can also sit directly in Content without a group.

<CommandMenu.Root open={open} onOpenChange={setOpen}>
  <CommandMenu.Content placeholder="Search">
    <CommandMenu.Group label="Projects">
      <CommandMenu.Item onSelect={() => setOpen(false)}>New project</CommandMenu.Item>
    </CommandMenu.Group>
  </CommandMenu.Content>
</CommandMenu.Root>

Open State

Pass open and onOpenChange to control the menu, or defaultOpen to let it manage itself. It stays open after onSelect, so close it there. defaultShortcut toggles it and defaults to Meta+K.

Current view: All projects
"use client";

import { Button, CommandMenu, Stack, Text } from "@vortexlabs/vortex";
import { useState } from "react";

export const ProjectCommands = () => {
  const [open, setOpen] = useState(false);
  const [view, setView] = useState("All projects");

  return (
    <Stack gap={3} align="center">
      <Button variant="secondary" onClick={() => setOpen(true)}>
        Open commands
      </Button>
      <Text attributes={{ role: "status" }}>Current view: {view}</Text>
      <CommandMenu.Root
        open={open}
        onOpenChange={setOpen}
        defaultShortcut="Control+Shift+K"
      >
        <CommandMenu.Content placeholder="Search project commands">
          <CommandMenu.Group label="Views">
            <CommandMenu.Item
              onSelect={() => {
                setView("My projects");
                setOpen(false);
              }}
            >
              Show my projects
            </CommandMenu.Item>
            <CommandMenu.Item
              onSelect={() => {
                setView("All projects");
                setOpen(false);
              }}
            >
              Show all projects
            </CommandMenu.Item>
          </CommandMenu.Group>
        </CommandMenu.Content>
      </CommandMenu.Root>
    </Stack>
  );
};

Variants

variant sets the backdrop style: dim (default), blurred, or transparent.

<CommandMenu.Content variant="dim">…</CommandMenu.Content>
<CommandMenu.Content variant="blurred">…</CommandMenu.Content>
<CommandMenu.Content variant="transparent">…</CommandMenu.Content>

Icons and Shortcuts

Add an icon and shortcut key labels to items. Labels are display only; register the action with useShortcuts.

import { PlusLarge, Search } from "@vortexlabs/vortex-icons";

<CommandMenu.Group label="Projects">
  <CommandMenu.Item icon={Search} onSelect={() => setOpen(false)}>
    Search projects
  </CommandMenu.Item>
  <CommandMenu.Item icon={PlusLarge} shortcut={["N"]} onSelect={() => setOpen(false)}>
    New project
  </CommandMenu.Item>
</CommandMenu.Group>

Search matches item labels. Add keywords for extra terms; they're required when an item has custom content. maxResults caps the results.

import { Hexagon } from "@vortexlabs/vortex-icons";

<CommandMenu.Content placeholder="Try team or billing" maxResults={3}>
  <CommandMenu.Group label="Workspace">
    <CommandMenu.Item
      icon={Hexagon}
      keywords={["team", "people"]}
      onSelect={() => setOpen(false)}
    >
      Members
    </CommandMenu.Item>
    <CommandMenu.Item
      keywords={["billing", "invoice", "payment"]}
      onSelect={() => setOpen(false)}
    >
      <Stack direction="row" gap={2} align="center">
        <Text size="body-sm-desktop">Billing</Text>
        <Text size="body-xs-desktop" color="subtle">
          Workspace owners
        </Text>
      </Stack>
    </CommandMenu.Item>
  </CommandMenu.Group>
</CommandMenu.Content>

Pass href to render an item as a link. Pressing Enter calls onSelect, not the link, so navigate in onSelect too.

<CommandMenu.Content placeholder="Search documentation">
  <CommandMenu.Group label="Get started">
    <CommandMenu.Item
      href="/docs/introduction"
      onSelect={() => window.location.assign("/docs/introduction")}
    >
      Introduction
    </CommandMenu.Item>
    <CommandMenu.Item
      href="/docs/quick-start"
      onSelect={() => window.location.assign("/docs/quick-start")}
    >
      Quick Start
    </CommandMenu.Item>
  </CommandMenu.Group>
</CommandMenu.Content>

Nested Menus

Keep the current section in state and switch the items. crumbs shows the path, and onCrumbClick and onBackClick go back.

const [section, setSection] = useState<"root" | "projects">("root");

<CommandMenu.Root open={open} onOpenChange={setOpen}>
  <CommandMenu.Content
    placeholder={section === "root" ? "Search commands" : "Search projects"}
    crumbs={section === "projects" ? ["Commands", "Projects"] : undefined}
    onCrumbClick={(index) => {
      if (index === 0) setSection("root");
    }}
    onBackClick={section === "projects" ? () => setSection("root") : undefined}
  >
    {section === "root" ? (
      <CommandMenu.Group label="Browse">
        <CommandMenu.Item onSelect={() => setSection("projects")}>
          Projects
        </CommandMenu.Item>
      </CommandMenu.Group>
    ) : (
      <CommandMenu.Group label="Projects">
        <CommandMenu.Item onSelect={() => setOpen(false)}>
          Website refresh
        </CommandMenu.Item>
        <CommandMenu.Item onSelect={() => setOpen(false)}>
          Mobile app
        </CommandMenu.Item>
      </CommandMenu.Group>
    )}
  </CommandMenu.Content>
</CommandMenu.Root>

Loading

While loading is true, loadingPlaceholder replaces the results.

<CommandMenu.Content
  placeholder="Search projects"
  loading={loading}
  loadingPlaceholder={
    <Stack gap={3} padding={5} align="center">
      <Text attributes={{ role: "status" }}>Loading projects…</Text>
      <Button size="sm" onClick={() => setLoading(false)}>
        Show loaded results
      </Button>
    </Stack>
  }
>
  <CommandMenu.Group label="Projects">
    <CommandMenu.Item onSelect={() => setOpen(false)}>
      Website refresh
    </CommandMenu.Item>
    <CommandMenu.Item onSelect={() => setOpen(false)}>
      Mobile app
    </CommandMenu.Item>
  </CommandMenu.Group>
</CommandMenu.Content>

Accessibility

Key
Description
Up arrowDown arrow
Moves through results while focus stays in the search.
Enter
Calls onSelect for the highlighted result.
Escape
Clears the search first. With an empty search, calls onBackClick when set, otherwise closes the menu.
Backspace
Calls onBackClick when the search is empty.
Use a descriptive placeholder, or set inputAriaLabel, to name the search field.