Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Navigation

Command Menu

Navigation

Command Menu React component for Tailwind CSS v4

BreadcrumbGlobal Search
Live props
Behavior
debounce200ms
shortcut
recent
recentKey
maxRecent5
globalShortcuts
loop
State
defaultOpen
defaultPage
defaultQuery
Content
placeholder
emptyText
loadingText
Appearance
footer
size
radius
highlightMatches
Layout
responsive
mode
inline
Code
import { CommandMenu } from "@/components/ui/command-menu"

<CommandMenu />
  1. Home
  2. Components
  3. Navigation
  4. Command Menu
Pro

Command Menu React component for Tailwind CSS v4

v1.0.1·Updated in 1.6.0

A React command menu (⌘K palette) with groups, shortcuts, nested pages, async results and recents, and a full-height sheet on phones.

Press ⌘K (Ctrl+K elsewhere) or click the search field to open it near the top of the screen. Type to filter: case and diacritics never matter and the matched letters turn bold; results from the server appear under the commands with a spinner in the input while they load. Arrow keys (and Ctrl+N / Ctrl+P) move, Home / End jump, Enter runs, and an item's own shortcut runs it while the menu is open. Items with a chevron open a page: its title appears as a chip before the input, Backspace on an empty input or Escape goes back, and Escape on the first page closes. Recently picked items come first next time (server results for the rest of the session). On a phone it opens as a full-height sheet with the keyboard already up, 48px rows and a Cancel button; swipe it down to close.

Command Menu is a Pro React component in the React navigation components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add command-menu, together with Badge, Drawer, Kbd, Spinner, Motion presets and 8 more and the npm packages @radix-ui/react-dialog, @radix-ui/react-visually-hidden, cmdk, lucide-react and motion.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add command-menu

Command Menu is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import CommandMenu from @/components/ui/command-menu.

"use client"

import { FilePlus2, Moon, Sun, UserPlus } from "lucide-react"
import { useTheme } from "next-themes"
import { CommandMenu, CommandMenuTrigger, commandItemsFromNav, type CommandGroup } from "@/components/ui/command-menu"
import type { NavItem } from "@/lib/navigation"

const nav: NavItem[] = [
  { label: "Clients", href: "/clients" },
  { label: "Invoices", href: "/invoices", items: [{ label: "Overdue", href: "/invoices/overdue" }] },
]

export function AppCommandMenu() {
  const { setTheme } = useTheme()
  const groups: CommandGroup[] = [
    { label: "Go to", items: commandItemsFromNav(nav) },
    {
      label: "Actions",
      items: [
        { id: "new-client", label: "New client", icon: <UserPlus />, href: "/clients/new" },
        { id: "new-invoice", label: "New invoice", icon: <FilePlus2 />, shortcut: "shift+mod+f", href: "/invoices/new" },
        { id: "theme", label: "Theme…", icon: <Moon />, page: "theme" },
      ],
    },
  ]
  return (
    <CommandMenu
      groups={groups}
      pages={[
        {
          id: "theme",
          title: "Theme",
          groups: [
            {
              items: [
                { id: "light", label: "Light", icon: <Sun />, onSelect: () => setTheme("light") },
                { id: "dark", label: "Dark", icon: <Moon />, onSelect: () => setTheme("dark") },
              ],
            },
          ],
        },
      ]}
      loadItems={async (query, { signal }) => {
        const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal })
        const clients: { id: string; name: string; city: string }[] = await res.json()
        return [{ label: "Clients", items: clients.map((c) => ({ id: c.id, label: c.name, description: c.city, href: `/clients/${c.id}` })) }]
      }}
      placeholder="Type a command or search…"
      trigger={<CommandMenuTrigger placeholder="Search or jump to…" />}
    />
  )
}

Props

  • groups
    CommandGroup[]

    The root page: groups of { id?, label?, items }. An item is { id, label, icon?, description?, badge?, keywords?, shortcut?, href?, external?, page?, onSelect?, tone?, disabled?, closeOnSelect? }. Build a “Go to” group from your navigation with commandItemsFromNav (see Parts).

  • pages
    CommandPage[]

    Pages pushed by items with page: { id, title, placeholder?, groups?, loadItems? }. A page's own groups and loader replace the root ones.

  • trigger
    ReactNode

    Rendered asChild as the trigger. CommandMenuTrigger (see Parts) is the ready-made search-field button.

  • loadItems
    (query: string, context: { signal: AbortSignal; page: string | null }) => Promise<CommandGroup[]>

    Async results shown after the static groups (clients, invoices). Debounced, and the previous request is aborted. On the root page it runs once the query is not empty; errors show a row with Try again.

  • debouncedefault 200
    number

    Milliseconds of quiet typing before loadItems runs.

  • defaultOpendefault false
    boolean

    Starts open. Use open and onOpenChange to control it.

  • open
    boolean

    Controlled open state; pair it with onOpenChange.

  • onOpenChange
    (open: boolean) => void

    Fires when the menu opens or closes.

  • page
    string | null

    The open page (null is the root), for opening straight into one. Pair it with onPageChange; defaultPage for uncontrolled.

  • defaultPagedefault ""
    string | null

    The page it opens on while uncontrolled (a page id; empty is the root, an unknown id is ignored). It returns there every time it reopens. The demo has the tema and limba pages.

  • onPageChange
    (page: string | null) => void

    Fires when a page is pushed or popped.

  • query
    string

    Controlled search text; defaultQuery and onQueryChange complete the pair.

  • defaultQuerydefault ""
    string

    Search text it opens with while uncontrolled; the input is back to it every time it reopens.

  • onQueryChange
    (query: string) => void

    Fires on every keystroke in the input (and when a page push or pop clears it).

  • shortcutdefault "mod+k"
    string

    Toggles the menu from anywhere (⌘K on Apple devices, Ctrl+K elsewhere). "" binds nothing. The showcase binds ⌘K itself, so the demo uses ⌘J while this is mod+k.

  • placeholderdefault "Type a command or search…"
    string

    The input placeholder on the root page; a page can set its own.

  • emptyTextdefault "No results"
    string

    Shown when nothing matches.

  • loadingTextdefault "Searching…"
    string

    Shown while loadItems runs and nothing else is listed yet; also the spinner's accessible name.

  • recentdefault true
    boolean

    A Recent group of the last picked items (by id) while the query is empty on the root page. Page pushers are not recorded.

  • recentKeydefault "command-menu:recent"
    string

    Local storage key for the recents (use-persisted-state). "" keeps them in memory for the session.

  • maxRecentdefault 5
    number

    How many recent items to show.

  • footerdefault true
    boolean

    The key hints bar (↑↓ navigate, ↵ open, esc close) and the result count. Fine pointers only; never on the phone sheet.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Width 480 / 640 / 800px (the sheet from 768px with mode sheet: 420 / 560 / 720px).

  • radiusdefault "lg"
    "none" | "sm" | "md" | "lg" | "full"

    Corner radius of the panel (0 / 12 / 16 / 24px) and, scaled down, of its rows and the phone search field. full keeps the 24px panel with pill rows.

  • highlightMatchesdefault true
    boolean

    Matched letters in bold. Matching ignores case and diacritics, so “cafe” highlights “Café”.

  • globalShortcutsdefault false
    boolean

    Also binds every item's shortcut on the page while mounted, even closed (a bare key only outside text fields). Off: item shortcuts work only while the menu is open.

  • loopdefault true
    boolean

    Arrow keys wrap from the last item to the first.

  • responsivedefault true
    boolean

    A full-height bottom sheet below 768px, with the keyboard raised at once.

  • modedefault "auto"
    "auto" | "dialog" | "sheet"

    auto: the dialog from 768px and the sheet below (with responsive). dialog or sheet: that at every width.

  • inlinedefault false
    boolean

    Renders the palette in place instead of a dialog (a help page, a sidebar search). open and the shortcut are ignored; Escape clears.

  • filter
    (item: CommandItem, query: string) => (number | null)

    Custom ranking: higher first, null hides. Default: text-match over the label, keywords, description and the shortcut, best first inside each group.

  • onSelect
    (item: CommandItem) => void

    After every pick (not page pushes), once the item's own onSelect settled.

  • linkComponent
    (props: LinkComponentProps) => ReactNode

    Router link for href items; defaults to the LinkProvider's. Clicks go through the link (cmd-click opens a new tab), Enter through useNavigate.

  • container
    HTMLElement | null

    Portal target, default document.body.

  • labels
    { title?; navigate?; select?; close?; back?; clear?; cancel?; recent?; error?; retry?; results?: (n) => string }

    Every other visible or spoken string: the hidden dialog title ("Command menu"), the footer hints (clear is the esc hint of an inline palette), the sheet's Cancel, the Recent heading, the error row and the announced result count.

  • classNames
    Partial<Record<"overlay" | "content" | "header" | "breadcrumb" | "input" | "spinner" | "esc" | "list" | "group" | "groupHeading" | "item" | "icon" | "label" | "description" | "badge" | "shortcut" | "empty" | "loading" | "error" | "footer", string>>

    Extra classes for the inner parts.

Parts

  • CommandMenuTrigger

    component

    A field-looking button (“Search or jump to… ⌘K”) to pass as the menu's trigger. It shows the enclosing menu's shortcut, folds to an icon button below 768px, and also opens a controlled menu.

    • placeholderdefault "Search or jump to…"
      string

      The text inside the field.

    • shortcut
      string

      The hint at the end. Defaults to the enclosing menu's shortcut (mod+k); "" hides it.

    • sizedefault "md"
      "sm" | "md" | "lg"

      Height 32 / 40 / 48px; md grows to 44px on touch screens.

    • variantdefault "soft"
      "soft" | "outline" | "ghost"

      A tinted field, a bordered field or a bare one for toolbars.

    • radiusdefault "md"
      "none" | "sm" | "md" | "lg" | "full"

      Corner shape, scaled with the size like Button.

    • icon
      ReactNode

      Replaces the search icon.

    • responsivedefault true
      boolean

      Folds to a square icon button below 768px.

    • classNames
      Partial<Record<"icon" | "label" | "shortcut", string>>

      Extra classes for the inner parts.

  • commandItemsFromNav

    function

    Turns the app's navigation (NavItem[]) into command items for a “Go to” group, keeping href, icon, badge, keywords and shortcut. Nested items read “Invoices › Overdue”, so typing “invoices overdue” finds them.

    • items*
      readonly NavItem[]

      The navigation tree, the same one the sidebar renders.

    • options.nesteddefault true
      boolean

      Include child items under their parent's label.

    • options.separatordefault " › "
      string

      Placed between a parent and a child label.

    • returns
      CommandItem[]

      Items ready for a group's items array.

  • commandItemText

    function

    The searchable text of an item: its label when it is a string, else its textValue, else its id. Useful to filter or sort items your own way.

  • CommandItem

    type

    One row: { id, label, icon?, description?, badge?, keywords?, textValue?, shortcut?, href?, external?, page?, onSelect?, tone?, disabled?, closeOnSelect? }.

  • CommandGroup

    type

    A titled list of items: { id?, label?, items }.

  • CommandPage

    type

    A nested page an item opens: { id, title, placeholder?, groups?, loadItems? }.

  • CommandMenuLabels

    type

    Every built-in string (title, footer hints, Cancel, Recent, errors, result count) for translation.

Examples

Nested pages

Items that open pages, two levels deep, with breadcrumb chips.

Async search

Server results with loading, empty and error + retry states.

Recents

The last picked items come first when you reopen it.

Sizes

sm, md and lg widths.

Phone

The full-height sheet with the keyboard raised at once.

Global shortcuts

Item shortcuts that work while the menu is closed.

People

Avatars as icons, with descriptions.

Dependencies

@radix-ui/react-dialog@radix-ui/react-visually-hiddencmdklucide-reactmotionbadgedrawerkbdspinnermotionnavigationtext-matchuse-controllable-stateuse-debounceuse-media-queryuse-persisted-stateuse-shortcutui-config

Files

  • components/ui/command-menu.tsx

FAQ

Is the React Command Menu component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Command Menu?

Run npx wingo-ui@latest add command-menu. It also adds the Wingo UI items it builds on (Badge, Drawer, Kbd, Spinner, Motion presets and 8 more) and the npm packages @radix-ui/react-dialog, @radix-ui/react-visually-hidden, cmdk, lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/command-menu.

How does Command Menu behave on phones?

A full-height bottom sheet below 768px, with the keyboard raised at once. The responsive prop controls this and is on by default.

See also

Global SearchKbdShortcuts DialoguseShortcuthookNavigationlibDialog

Used in

File SystemAdmin ShellDocs Home Page

More React navigation components

Nav TabsPaginationStepsTable Of ContentsTabsTopbar
All 19 React navigation components

Articles using this component

  • React Command Palette: Cmd+K, Nested Pages and Mobile

    Component guides · Oct 9, 2026 · 9 min read

PreviousBreadcrumbNext Global Search
SSummit CRM

/

Home

Receivables$248,730.00
Overdue3
Open quotes12

Activity

Nothing yet. Open the menu and pick a command.

shortcut mod+j · debounce 200ms · recent on (5, “command-menu:recent”) · loop on · highlight on · global shortcuts off

Press CtrlJControl J here (the site keeps CtrlKControl K), or click the field. Try “overdue invoices”, “boston” or “theme”.

Tap the search icon for the full-height sheet. Try “overdue invoices”, “boston” or “theme”.