Command
A command palette that allows users to search and execute commands. Built on top of cmdk.
Command.DialogRoot owns the palette: its open state, its query text, and the ⌘K shortcut. Command.SearchTrigger wraps whatever control opens it and makes that control behave like a search field: typing or pasting into it opens the palette with your text already in the query. It renders no DOM of its own, so the appearance is always the child's: in a sidebar that child is Sidebar.SearchTrigger, which owns how the row looks in both panel states.
For a text field that filters options in place rather than opening a modal, the component is Combobox, not this one.
Search trigger example
Command.SearchTrigger gives its child the dialog wiring and the typing behavior. Give the button focus and start typing: the palette opens seeded with what you typed. Pasting works too.
Inline example
Command.Root is the always-visible palette: the same list and filtering without a dialog around it.
Button trigger example
When the trigger should not behave like a search field at all, use Command.DialogTrigger with your own element. It carries no typing behavior; wrap the element in Command.SearchTrigger instead when it should.
Composition
Compose the parts of a Command together to build your own. Command.SearchTrigger and Command.DialogTrigger are alternatives; use one or the other, not both:
Polymorphism
Command.Input, Command.Item, Command.Separator, and Command.Empty accept asChild, forwarded to the underlying cmdk primitive. Command.DialogTrigger accepts it too, forwarded to its Radix dialog trigger.
The common case is Command.Item asChild with an <a> or a router Link, so selecting a result navigates while the item keeps its filtering, keyboard selection, and highlight behavior. Command.DialogTrigger asChild is the other frequent one: it lets any element, usually a Button, open the command dialog.
Command.DialogRoot and Command.DialogContent own the dialog's state and panel, and Command.Shortcut is a plain <span> for key hints, so none of those take asChild. Command.SearchTrigger has no asChild prop for the opposite reason: cloning is all it does, so its single-element children is always the trigger and there is nothing to opt into.
The keyboard shortcut
Command.DialogRoot binds ⌘K on macOS and Ctrl+K everywhere else, and toggles the palette. You do not need a hotkey library, and you should not add one.
It lives on the root rather than on the trigger because the trigger can be unmounted while the shortcut must still work: a sidebar rendered as a mobile sheet unmounts its whole tree when the sheet is closed.
What it deliberately does:
- Requires exactly the platform modifier.
⌘on Apple platforms,Ctrlelsewhere; the two never substitute for each other, so macOS's nativeCtrl+K("kill to end of line") keeps working.⌘⇧Kand friends pass through to the browser. - Claims the event. Firefox binds
⌘Kto its own search bar, so a shortcut that does notpreventDefault()runs both actions. - Has exactly one owner per window. The first mounted root that wants the shortcut gets it; later roots queue and take over only when that one unmounts. Two palettes never open on one keypress.
- Yields inside editors. The chord is ignored while focus is in a
contenteditablehost or a<textarea>, where⌘Kis already "insert link" or a chord prefix, and where Monaco and CodeMirror attach. It still fires from a plain<input>, including the palette's ownCommand.Input, so the chord closes what it opened. - Yields to a handler that already ran. Inside an open palette on Windows and Linux, cmdk's own vim bindings map
Ctrl+Kto "previous item" and claim the event, so the chord moves the selection instead of closing the palette.Escape, the close button, and clicking away all still dismiss it.
Pass keyboardShortcut={false} to bind the chord yourself with useCommandDialog(). Command.SearchTrigger then adds no aria-keyshortcuts, and the hook reports the same flag as keyboardShortcut so your trigger can drop its visible ⌘K hint too. The UI never advertises a binding mantle does not own.
Who owns the query
By default the palette owns its query text, which is what makes seeding work: the seed and the open state land in one state update, so Command.Input mounts with the character already in it rather than racing the dialog's mount. Every open starts from a known query (the seed, or empty), so dismissing and reopening never resurrects the previous search.
Read it with useCommandDialog() when your palette does its own matching:
Passing value to Command.Input takes the query over completely: your value wins, the hook stops tracking the field, and seeding becomes yours to apply. Prefer the hook.
In a sidebar
In a sidebar the search row is a Sidebar.SearchTrigger: the same row chrome as a Sidebar.ItemButton, with a ⌘K hint that appears on hover or keyboard focus and is otherwise invisible. It is deliberately a row and not a text field (a search entry point in a sidebar is one of the rows, not a form control wedged among them), which is also why it becomes the same icon chip as its neighbors once the panel collapses to the rail. All of that is owned by Sidebar, where the rail lives, and Command.SearchTrigger supplies only the behavior. Wrap the pair in Sidebar.Tooltip so the row still has a visible label in the rail.
Collapse the panel with the toggle to watch the row become a chip. The preview is its own document, so ⌘K works inside it.
The row goes in Sidebar.Body rather than Sidebar.Header so it shares the item rows' column and collapses into the same icon stack. Sidebar.SearchTrigger marks the shortcut hint aria-hidden (Command.SearchTrigger's aria-keyshortcuts already announces the chord) and drops it entirely in the rail, where the label is clipped rather than removed so the button keeps its accessible name as a chip.
Both searchActionFromKeyDown and searchActionFromPaste are exported for a trigger that cannot use Command.SearchTrigger at all; they take the native event (event.nativeEvent), because React's synthetic keyboard event does not carry isComposing.
Common mistakes
Do not bind ⌘K yourself. Command.DialogRoot does it, and it handles the parts a hand-rolled listener usually misses: claiming the event, keeping the two platform modifiers distinct, one owner per window, and the editor carve-out. If you must own the chord, turn mantle's off explicitly:
Do not make the trigger a real text field. A trigger that opens a modal palette is a <button>; that is why Command.SearchTrigger's child stays one and announces "has popup dialog". A <input readonly> that looks editable, or a role="combobox" without the combobox keyboard contract, both promise an interaction the palette does not implement. If you want results to filter in place under a real field, the component is Combobox.
API Reference
The Command component is built on top of cmdk and ships a complete set of sub-components for command palettes.
Command.Root
The inline palette, wrapping Command.Input and Command.List. It owns the filtering and the active item for every part below it — Command.DialogContent renders one itself, so a modal palette does not add it.
All props from cmdk's Command Root.
Command.DialogRoot
The state owner for the Command dialog. Owns the open state, the query text, and the ⌘K shortcut, and renders no DOM of its own.
Command.SearchTrigger
Makes any control behave like a search field that opens the palette. It renders no DOM of its own — it clones its child — so presentation belongs entirely to the child, and this part never has an opinion about how the trigger looks or how it behaves in a collapsed sidebar rail.
What it contributes to that child:
aria-haspopup="dialog",aria-expanded,aria-controls, and focus restoration on close, from theDialog.Triggerit composes. Inside aSheet(aSidebar.Navbelow itsmobileBreakpoint) the trigger still binds toCommand.DialogRoot, becauseSheetparts read a dialog scope of their own.data-state="open" | "closed", stamped from the palette's own state rather than inherited, so wrapping this in another cloning trigger cannot replace it.aria-keyshortcutsfor the⌘Kbinding — present only whenCommand.DialogRootowns the shortcut, so it can never advertise a binding that is not bound. The server cannot know the platform, so the server HTML and the hydration render carryControl+K. On an Apple host, React corrects it toMeta+Konce after hydration. A trigger that mounts on the client after hydration renders the right chord in its first render, likeMetaKey. Pass your ownaria-keyshortcutsto announce a chord you bound yourself.onKeyDownandonPaste, composed with the child's own handlers — the child's run first, and apreventDefault()there vetoes the open.data-slot="command-search-trigger", joined ahead of the child's own slot.
It adds no accessible name: the child's visible label is the name, so the two cannot drift apart. Render the ⌘K hint inside the child as aria-hidden, since aria-keyshortcuts already announces the chord.
Command.DialogTrigger
A bare button that opens the Command dialog when clicked — the escape hatch for a trigger that should not look like a search field. Supports asChild for custom trigger elements.
It carries none of Command.SearchTrigger's behavior: no aria-keyshortcuts, and typing or pasting into it does nothing. Wrap your element in Command.SearchTrigger instead when it should behave like a search field.
All props from Radix UI's Dialog.Trigger.
Command.DialogContent
The visible content of the Command dialog. Renders inside the dialog portal and wraps the command palette UI.
When the palette opens, this part takes over the dialog's initial focus so the caret lands at the end of a seeded query. Radix's own open-autofocus selects the focused field's whole value, which would make the first keystroke after a seeded open replace the seed instead of extending it. It finds the field by Command.Input's data-slot="command-input", and leaves Radix's default in place when there is no input or the input is disabled.
Command.Input
The palette's query field.
Inside a Command.DialogRoot the palette owns the query by default — see Who owns the query. Under a bare Command.Root it stays uncontrolled and cmdk owns the text. ref lands on the wrapper; className and rest props land on the <input>.
All other props from cmdk's Command Input.
Command.List
The scrolling container for the palette's groups and items.
All props from cmdk's Command List.
Command.Empty
The empty-state message; it renders only when no item matches the query.
It renders nothing in the server HTML and in the hydration pass. Items register with cmdk in a layout effect, which never runs on the server, so the server counts zero matches for every list. A palette with no items shows its empty state one render after hydration.
All props from cmdk's Command Empty.
Command.Group
A labeled section of items; the heading prop names it.
All props from cmdk's Command Group.
Command.Item
One selectable row in the palette's list.
All props from cmdk's Command Item.
Command.Separator
A visual separator between command groups or items. Automatically hidden when there is an active search query.
All props from cmdk's Command Separator.
Command.Shortcut
Displays a keyboard shortcut hint within a command item. Renders as a styled span element.
All props from the HTML span element.
useCommandDialog
Reads the nearest Command.DialogRoot state. Throws when called outside one, so a trigger that opens nothing fails loudly instead of silently.
Reach for it to open the palette from somewhere other than its trigger — a sidebar row, an empty state, a "search everything for this" item — or to read the query for your own matching.
The label is one template literal rather than text around a {query} expression. Command.DialogRoot resets
the query every time the palette opens, so a bare interpolation between two literal siblings makes React
remove a text node — and a browser translation engine has reparented it first, so the removal throws and the
page goes blank. One interpolated string is a lone child, which React writes through textContent instead.
Browser translation covers the mechanism.
searchActionFromKeyDown
Classifies a keydown on a search trigger into a SearchAction: { type: "seed", query } for text, { type: "open" } for text intent that cannot be replayed, { type: "ignore" } for everything else. Pure, and takes the native event.
searchActionFromPaste
Classifies a paste on a search trigger. A paste is unambiguous search intent, so it always opens the palette, and seeds it whenever the clipboard holds text — never returns "ignore". Multi-line text is collapsed to one space-separated line, because the query is a one-line field. Pure, and takes the native event.
MetaKey
Renders the platform-appropriate meta key glyph (⌘ for macOS/iOS, ⌃ for other platforms) with a visually hidden "Command" or "Control" label. It detects the platform on mount and is SSR-safe, defaulting to ⌃ to avoid hydration mismatches. A MetaKey that mounts after hydration, such as inside a palette that opens later, renders the host's glyph in its first render.
Use it in keyboard shortcut hints and Command.Shortcut labels so each platform shows its own modifier key.
useCommandState
A hook for accessing the command palette state.
All props from cmdk's useCommandState.