npm.io
2.0.0 • Published yesterday

base-ui-cmdk

Licence
MIT
Version
2.0.0
Deps
0
Size
37 kB
Vulns
0
Weekly
0

base-ui-cmdk

npm version license

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 — includes Command.Input, Command.List, Command.Item, etc.
Named exports
  • CommandRoot
  • CommandDialog
  • CommandInput
  • CommandList
  • CommandItem
  • CommandGroup
  • CommandSeparator
  • CommandEmpty
  • CommandLoading
  • useCommandState
  • defaultFilter
Command props
Prop Default Description
shouldFilter true Automatic filtering and sorting
filter commandScore Custom rank function (01)
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

Keywords