A package with components for building your dream command palette for your web application.
Watch the YouTube demo or try it out here to get started.
âś“ Accessible
âś“ Flexible
âś“ Good looking
âś“ Very fast
âś“ Dark & light mode
npm install react-cmdk
Or if you'd rather use Yarn
yarn add react-cmdk
You can compose your command palette pretty much however you like with the included components. But here is an example of a command palette that uses some of the included helpers for a very neat solution.
import "react-cmdk/dist/cmdk.css";
import CommandPalette, { filterItems, getItemIndex } from "react-cmdk";
import { useState } from "react";
const Example = () => {
const [page, setPage] = useState<"root" | "projects">("root");
const [open, setOpen] = useState<boolean>(true);
const [search, setSearch] = useState("");
const filteredItems = filterItems(
[
{
heading: "Home",
id: "home",
items: [
{
id: "home",
children: "Home",
icon: "HomeIcon",
href: "#",
},
{
id: "settings",
children: "Settings",
icon: "CogIcon",
href: "#",
},
{
id: "projects",
children: "Projects",
icon: "RectangleStackIcon",
closeOnSelect: false,
onClick: () => {
setPage("projects");
},
},
],
},
{
heading: "Other",
id: "advanced",
items: [
{
id: "developer-settings",
children: "Developer settings",
icon: "CodeBracketIcon",
href: "#",
},
{
id: "privacy-policy",
children: "Privacy policy",
icon: "LifebuoyIcon",
href: "#",
},
{
id: "log-out",
children: "Log out",
icon: "ArrowRightOnRectangleIcon",
onClick: () => {
alert("Logging out...");
},
},
],
},
],
search
);
return (
<CommandPalette
onChangeSearch={setSearch}
onChangeOpen={setOpen}
search={search}
isOpen={open}
page={page}
>
<CommandPalette.Page id="root">
{filteredItems.length ? (
filteredItems.map((list) => (
<CommandPalette.List key={list.id} heading={list.heading}>
{list.items.map(({ id, ...rest }) => (
<CommandPalette.ListItem
key={id}
index={getItemIndex(filteredItems, id)}
{...rest}
/>
))}
</CommandPalette.List>
))
) : (
<CommandPalette.FreeSearchAction />
)}
</CommandPalette.Page>
<CommandPalette.Page id="projects">
{/* Projects page */}
</CommandPalette.Page>
</CommandPalette>
);
};
export default Example;
The package does include a helper hook for opening the command palette, but you can actually open it however you want. Here are some examples.
const [isOpen, setIsOpen] = useState<boolean>(false);
useHandleOpenCommandPalette(setIsOpen);
const [isOpen, setIsOpen] = useState<boolean>(false);
useEffect(() => {
function handleKeyDown(e: KeyboardEvent) {
if (
(navigator?.platform?.toLowerCase().includes("mac")
? e.metaKey
: e.ctrlKey) &&
e.key === "k"
) {
e.preventDefault();
e.stopPropagation();
setIsOpen((currentValue) => {
return !currentValue;
});
}
}
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, []);
name | type | required | default | description |
---|---|---|---|---|
onChangeSearch | (value: string) => void | true | Function for setting search value | |
onChangeOpen | (value: boolean) => void | true | Function for setting open state | |
children | React.ReactNode | true | Children of command palette | |
isOpen | boolean | true | Open state | |
search | string | true | Search state | |
placeholder | string | false | "Search" |
Search field placeholder |
page | string | false | The current page id | |
renderLink | RenderLink | false | Function for customizing rendering of links | |
footer | React.ReactNode | false | Footer component | |
selected | number | false | The current selected item index | |
onChangeSelected | (value: number) => void | false | Function for setting selected item index |
FYI. Using pages is completely optional
name | type | required | default | description |
---|---|---|---|---|
id | string | true | A unique page id | |
children | React.ReactNode | true | Children of the list | |
searchPrefix | string[] | false | Prefix to the left of the search bar | |
onEscape | () => void | false | Function that runs upon clicking escape |
name | type | required | default | description |
---|---|---|---|---|
children | React.ReactNode | true | Children of the list | |
heading | string | false | Heading of the list |
name | type | required | default | description |
---|---|---|---|---|
index | number | true | Index for list item | |
closeOnSelect | boolean | false | Whether to close the command palette upon click | |
icon | (IconName, React.FC) | false | false |
Icon for list item |
iconType | IconType | false | "solid" |
Icon for list item |
showType | boolean | false | true | Whether to show the item type |
disabled | boolean | false | Whether the item is disabled | |
keywords | Array | false | Underlying search keywords for the list item |
The list item also extends the HTMLAnchorElement & HTMLButtonElement
types
name | type | required | default | description |
---|---|---|---|---|
index | number | false | 0 |
Index for list item |
label | string | false | "Search for" |
Button label |
The search action also extends the HTMLAnchorElement & HTMLButtonElement
types
(
props: DetailedHTMLProps<
AnchorHTMLAttributes<HTMLAnchorElement>,
HTMLAnchorElement
>
) => ReactNode;
Array of
name | type | required | default | description |
---|---|---|---|---|
id | string | true | Id for list | |
items | Array<JsonStructureItem > |
true | Items for list | |
heading | string | false | Heading for list |
CommandPalette.ListItem
Omits index
& extends
name | type | required | default | description |
---|---|---|---|---|
id | string | true | Id for list item |
A function for getting the current index of a item within the json structure
(items: JsonStructure, listItemId: string, startIndex = 0) => number;
A function for filtering the json structure from a search string
(
items: JsonStructure,
search: string,
options?: { filterOnListHeading: boolean }
) => JsonStructure;
A function for rendering a json structure
(items: JsonStructure) => JSX.Element[]
(fn: React.Dispatch<React.SetStateAction<boolean>>) => void