"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.
"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
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>Links
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
onSelect for the highlighted result.onBackClick when set, otherwise closes the menu.onBackClick when the search is empty.placeholder, or set inputAriaLabel, to name the search field.