base-ui-cmdk
base-ui-cmdk is a Base UI implementation of the cmdk command menu API.
The original cmdk is built on Radix UI primitives; this package keeps the same composable cmdk feel (Command, Command.Input, Command.List, etc.) while using @base-ui/react for dialog primitives.
Installation
pnpm add base-ui-cmdk @base-ui/react react react-dom
npm install base-ui-cmdk @base-ui/react react react-dom
yarn add base-ui-cmdk @base-ui/react react react-dom
bun add base-ui-cmdk @base-ui/react react react-dom
Peer dependencies
| Package | Version |
|---|---|
react |
^19 |
react-dom |
^19 |
@base-ui/react |
^1 |
Quick start
import { Command } from "base-ui-cmdk";
export function CommandMenu() {
return (
<Command label="Command Menu">
<Command.Input placeholder="Type a command..." />
<Command.List>
<Command.Empty>No results found.</Command.Empty>
<Command.Group heading="Actions">
<Command.Item onSelect={() => console.log("Open")}>Open</Command.Item>
<Command.Item onSelect={() => console.log("Save")}>Save</Command.Item>
</Command.Group>
</Command.List>
</Command>
);
}
Dialog usage
import * as React from "react";
import { Command } from "base-ui-cmdk";
export function GlobalCommandMenu() {
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "k" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
setOpen((current) => !current);
}
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, []);
return (
<Command.Dialog open={open} onOpenChange={setOpen} label="Global Command Menu">
<Command.Input placeholder="Search..." />
<Command.List>
<Command.Empty>No results found.</Command.Empty>
<Command.Item>Open Settings</Command.Item>
<Command.Item>Open Profile</Command.Item>
</Command.List>
</Command.Dialog>
);
}
Migrating from cmdk
Replace the package import and install @base-ui/react instead of Radix Dialog:
- import { Command } from "cmdk"
+ import { Command } from "base-ui-cmdk"
Component APIs are intentionally aligned with cmdk. Styling via cmdk-* attributes is unchanged.
Command.Dialog uses Base UI (Root, Portal, Backdrop, Viewport, Popup) and supports viewportClassName for the viewport wrapper.
API
Namespace export
Command— includesCommand.Input,Command.List,Command.Item, etc.
Named exports
CommandRootCommandDialogCommandInputCommandListCommandItemCommandGroupCommandSeparatorCommandEmptyCommandLoadinguseCommandStatedefaultFilter
Command props
| Prop | Default | Description |
|---|---|---|
shouldFilter |
true |
Automatic filtering and sorting |
filter |
commandScore |
Custom rank function (0–1) |
loop |
false |
Wrap keyboard selection |
vimBindings |
true |
Ctrl+n/j/p/k navigation |
async |
false |
Defer auto-selection while options load |
fetchInProgress |
false |
Skip selection until fetch completes |
triggerKeys |
["Enter"] |
Keys that activate the selected item |
disablePointerSelection |
false |
Disable hover-to-select |
Styling
Parts expose cmdk-* attributes compatible with existing cmdk-style selectors:
[cmdk-root][cmdk-input][cmdk-list][cmdk-item][cmdk-group][cmdk-group-heading][cmdk-separator][cmdk-empty][cmdk-loading][cmdk-dialog][cmdk-overlay][cmdk-viewport]
Animate list height with the CSS variable set by Command.List:
[cmdk-list] {
height: var(--cmdk-list-height);
transition: height 100ms ease;
}
Custom trigger keys
By default, Enter selects the highlighted item. Pass triggerKeys to use other keys (for example Tab in a combobox-style menu):
<Command triggerKeys={["Enter", "Tab"]}>
<Command.Input />
<Command.List>
<Command.Item>Apple</Command.Item>
</Command.List>
</Command>
Async options
When options load asynchronously, enable async mode so selection does not jump to the first item on every keystroke. Set fetchInProgress while a request is in flight; the first item is selected once fetching completes.
const [search, setSearch] = React.useState("");
const [loading, setLoading] = React.useState(false);
const [items, setItems] = React.useState<string[]>([]);
React.useEffect(() => {
let cancelled = false;
async function load(query: string) {
setLoading(true);
const results = await fetchItems(query);
if (!cancelled) {
setItems(results);
setLoading(false);
}
}
load(search);
return () => {
cancelled = true;
};
}, [search]);
return (
<Command async fetchInProgress={loading}>
<Command.Input value={search} onValueChange={setSearch} />
<Command.List>
{loading && <Command.Loading>Fetching…</Command.Loading>}
{items.map((item) => (
<Command.Item key={item} value={item}>
{item}
</Command.Item>
))}
</Command.List>
</Command>
);
Development
pnpm install
pnpm typecheck
pnpm build
Publishing
pnpm build
npm publish
Requires an npm account with access to the base-ui-cmdk package name. Enable npm provenance by publishing from GitHub Actions or with --provenance locally.
License
MIT nunesunil