zeroUI

Command

A search box over a list of actions, always open: type to narrow it, arrows to move, Enter to run. CommandDialog floats it over the dimmed page.

Install

terminal
# once per project: where @zeroui items come frompnpm dlx shadcn@latest registry add @zeroui=https://zeroui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zeroui/command

Examples

Inline, and as a ⌘K palette

Take the job⌘T
Hand in
Abandon
Job board
District map
Drivers
command-palette.tsx
import { useEffect, useState } from "react";import { Button } from "@/components/zero/button";import {  Command,  CommandCollection,  CommandDialog,  CommandEmpty,  CommandGroup,  CommandGroupLabel,  CommandInput,  CommandItem,  CommandList,  CommandShortcut,} from "@/components/zero/command";type Action = { value: string; label: string; keys?: string };const groups: { value: string; items: Action[] }[] = [  {    value: "Jobs",    items: [      { value: "take", label: "Take the job", keys: "⌘T" },      { value: "hand-in", label: "Hand in" },      { value: "abandon", label: "Abandon" },    ],  },  {    value: "Go to",    items: [      { value: "board", label: "Job board" },      { value: "map", label: "District map" },      { value: "drivers", label: "Drivers" },    ],  },];function Palette({ onRun }: { onRun?: () => void }) {  return (    <Command items={groups}>      <CommandInput placeholder="Type a command" aria-label="Command" />      <CommandList>        {(group: (typeof groups)[number]) => (          <CommandGroup key={group.value} items={group.items}>            <CommandGroupLabel>{group.value}</CommandGroupLabel>            <CommandCollection>              {(item: Action) => (                <CommandItem key={item.value} value={item} onClick={onRun}>                  {item.label}                  {item.keys ? (                    <CommandShortcut>{item.keys}</CommandShortcut>                  ) : null}                </CommandItem>              )}            </CommandCollection>          </CommandGroup>        )}      </CommandList>      <CommandEmpty>Nothing matches.</CommandEmpty>    </Command>  );}export default function CommandPalette() {  const [open, setOpen] = useState(false);  useEffect(() => {    const onKeyDown = (event: KeyboardEvent) => {      if (event.key === "k" && (event.metaKey || event.ctrlKey)) {        event.preventDefault();        setOpen((current) => !current);      }    };    window.addEventListener("keydown", onKeyDown);    return () => window.removeEventListener("keydown", onKeyDown);  }, []);  return (    <div className="grid w-full max-w-md justify-items-start gap-4">      <Palette />      <Button variant="secondary" onClick={() => setOpen(true)}>        Open the palette (⌘K)      </Button>      <CommandDialog open={open} onOpenChange={setOpen}>        <Palette onRun={() => setOpen(false)} />      </CommandDialog>    </div>  );}

Behaviour

The first match is always highlighted, so Enter runs it. The search sits on a black bar; the rows are the Dropdown menu's, filled with the accent while highlighted. Pass the items, or groups of items, to Command and render them from CommandList's function child; an item's label is what typing matches.

It is built on Base UI's Autocomplete, so it needs no other library. The ⌘K shortcut is yours to wire, as in the example.

Props

Command

PropTypeDefaultDescription
itemsItem[] | Group[]—What to list; each item needs a label.
filter(item, query) => boolean—Replaces the built-in matching.

Also takes every prop of Base UI's Autocomplete.Root.

CommandDialog

PropTypeDefaultDescription
open / onOpenChangeboolean / (open) => void—Open, controlled.
titlestring"Command palette"The dialog's accessible name.

CommandInput, CommandList, CommandEmpty, CommandGroup, CommandGroupLabel, CommandCollection, CommandItem, CommandSeparator, CommandShortcut

Also takes every prop of Base UI's Autocomplete.Input.