base-ui-cmdk
v1.0.0
Published
Base UI implementation of the cmdk command menu — composable, unstyled, and accessible.
Maintainers
Readme
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-domnpm install base-ui-cmdk @base-ui/react react react-domyarn add base-ui-cmdk @base-ui/react react react-dombun add base-ui-cmdk @base-ui/react react react-domPeer 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 buildPublishing
pnpm build
npm publishRequires 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
