Overlay

Command

A ⌘K palette: Base UI Autocomplete inline inside a dialog. Search, arrow, Enter, done.

Show code
"use client";

import { useEffect, useState } from "react";

import { Button } from "@/registry/lilt/ui/button";
import {
  Command,
  CommandCollection,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandGroupLabel,
  CommandInput,
  CommandItem,
  CommandList,
  CommandTrigger,
} from "@/registry/lilt/ui/command";
import { Kbd, KbdGroup } from "@/registry/lilt/ui/kbd";

interface CommandEntry {
  value: string;
  label: string;
  shortcut?: string[];
}

interface CommandGroupEntry {
  value: string;
  items: CommandEntry[];
}

const groupedCommands: CommandGroupEntry[] = [
  {
    items: [
      { label: "Garden journal", value: "garden-journal" },
      { label: "Seed library", value: "seed-library" },
      { label: "Watering schedule", value: "watering-schedule" },
      { label: "Pressed flowers", value: "pressed-flowers" },
    ],
    value: "Pages",
  },
  {
    items: [
      {
        label: "Plant a new note",
        shortcut: ["⌘", "N"],
        value: "plant-a-note",
      },
      {
        label: "Water everything",
        shortcut: ["⌘", "W"],
        value: "water-everything",
      },
      {
        label: "Press a flower for later",
        shortcut: ["⌘", "P"],
        value: "press-a-flower",
      },
    ],
    value: "Actions",
  },
];

export default function CommandDemo() {
  const [open, setOpen] = useState(false);

  useEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      if ((event.metaKey || event.ctrlKey) && event.key === "k") {
        event.preventDefault();
        setOpen((previous) => !previous);
      }
    };
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
  }, []);

  return (
    <Command onOpenChange={setOpen} open={open}>
      <CommandTrigger render={<Button variant="secondary" />}>
        Search anything...
        <KbdGroup>
          <Kbd>⌘</Kbd>
          <Kbd>K</Kbd>
        </KbdGroup>
      </CommandTrigger>
      <CommandDialog items={groupedCommands}>
        <CommandInput />
        <CommandEmpty />
        <CommandList>
          {(group: CommandGroupEntry) => (
            <CommandGroup items={group.items} key={group.value}>
              <CommandGroupLabel>{group.value}</CommandGroupLabel>
              <CommandCollection>
                {(item: CommandEntry) => (
                  <CommandItem
                    key={item.value}
                    onClick={() => setOpen(false)}
                    shortcut={item.shortcut}
                    value={item}
                  >
                    {item.label}
                  </CommandItem>
                )}
              </CommandCollection>
            </CommandGroup>
          )}
        </CommandList>
      </CommandDialog>
    </Command>
  );
}

Installation

npx shadcn@latest add @lilt/command

Accessibility

  • One dialog, one combobox: focus lands in the search input, arrows walk the list, Enter runs the highlighted command, Escape closes.
  • The empty state is announced politely, and the trigger advertises the ⌘K shortcut as text, not just a glyph.