Skip to content
SevenUI
Esc
navigateopen⌘Jpreview
Sign in
On this page

Command

A fast, composable command palette built on the Base UI Autocomplete primitive.

"use client";

import * as React from "react";

import {
  CalculatorIcon,
  CalendarIcon,
  ClockIcon,
  CreditCardIcon,
  FileTextIcon,
  LogOutIcon,
  SettingsIcon,
  SmileIcon,
  UserIcon,
} from "lucide-react";

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandShortcut,
} from "@/registry/base/ui/command";

type Item = {
  value: string;
  label: string;
  icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
  shortcut?: string;
};

type Group = { value: string; items: Item[] };

const commandGroups: Group[] = [
  {
    value: "Suggestions",
    items: [
      { value: "calendar", label: "Calendar", icon: CalendarIcon },
      { value: "search-emoji", label: "Search Emoji", icon: SmileIcon },
      { value: "calculator", label: "Calculator", icon: CalculatorIcon },
      { value: "new-document", label: "New Document", icon: FileTextIcon },
      { value: "recent-files", label: "Recent Files", icon: ClockIcon },
    ],
  },
  {
    value: "Settings",
    items: [
      {
        value: "profile",
        label: "Profile",
        icon: UserIcon,
        shortcut: "⇧⌘P",
      },
      {
        value: "billing",
        label: "Billing",
        icon: CreditCardIcon,
        shortcut: "⌘B",
      },
      {
        value: "settings",
        label: "Settings",
        icon: SettingsIcon,
        shortcut: "⌘S",
      },
      {
        value: "log-out",
        label: "Log out",
        icon: LogOutIcon,
        shortcut: "⇧⌘Q",
      },
    ],
  },
];

export default function CommandDemo() {
  return (
    <Command items={commandGroups} className="max-w-md">
      <CommandInput placeholder="Type a command or search..." />
      <CommandList>
        {(group: Group) => (
          <CommandGroup key={group.value} heading={group.value} items={group.items}>
            {(item: Item) => (
              <CommandItem key={item.value} value={item}>
                <item.icon />
                {item.label}
                {item.shortcut && (
                  <CommandShortcut>{item.shortcut}</CommandShortcut>
                )}
              </CommandItem>
            )}
          </CommandGroup>
        )}
      </CommandList>
      <CommandEmpty>No results found.</CommandEmpty>
    </Command>
  );
}

Installation

npx shadcn@latest add https://sevenui.dev/r/command.json

Usage

"use client";

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command";

const fruits = [
  { value: "apple", label: "Apple" },
  { value: "banana", label: "Banana" },
  { value: "blueberry", label: "Blueberry" },
];

export default function CommandDemo() {
  return (
    <Command items={fruits}>
      <CommandInput placeholder="Type a command or search..." />
      <CommandList>
        {(item: (typeof fruits)[number]) => (
          <CommandItem key={item.value} value={item}>
            {item.label}
          </CommandItem>
        )}
      </CommandList>
      <CommandEmpty>No results found.</CommandEmpty>
    </Command>
  );
}

Dialog

CommandDialog wraps Command in a Base UI Dialog so the palette can be toggled with a keyboard shortcut and rendered as a modal overlay. Because the dialog unmounts its content on close, filter and highlight state reset for free every time it reopens.

"use client";

import * as React from "react";

import { Button } from "@/registry/base/ui/button";
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandShortcut,
} from "@/registry/base/ui/command";

type Item = { value: string; label: string; shortcut?: string };
type Group = { value: string; items: Item[] };

const commandGroups: Group[] = [
  {
    value: "Suggestions",
    items: [
      { value: "calendar", label: "Calendar" },
      { value: "search-emoji", label: "Search Emoji" },
      { value: "calculator", label: "Calculator" },
      { value: "new-document", label: "New Document" },
      { value: "recent-files", label: "Recent Files" },
    ],
  },
  {
    value: "Settings",
    items: [
      { value: "profile", label: "Profile", shortcut: "⇧⌘P" },
      { value: "billing", label: "Billing", shortcut: "⌘B" },
      { value: "settings", label: "Settings", shortcut: "⌘S" },
      { value: "log-out", label: "Log out", shortcut: "⇧⌘Q" },
    ],
  },
];

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

  React.useEffect(() => {
    function onKeyDown(event: KeyboardEvent) {
      if (event.key === "k" && (event.metaKey || event.ctrlKey)) {
        event.preventDefault();
        setOpen((prev) => !prev);
      }
    }

    document.addEventListener("keydown", onKeyDown);
    return () => document.removeEventListener("keydown", onKeyDown);
  }, []);

  return (
    <div className="flex flex-col items-center gap-3">
      <p className="text-sm text-muted-foreground">
        Press ⌘K or click below
      </p>
      <Button variant="outline" onClick={() => setOpen(true)}>
        Open command palette
      </Button>
      <CommandDialog open={open} onOpenChange={setOpen}>
        <Command items={commandGroups} className="rounded-lg border-none">
          <CommandInput placeholder="Type a command or search..." />
          <CommandList>
            {(group: Group) => (
              <CommandGroup
                key={group.value}
                heading={group.value}
                items={group.items}
              >
                {(item: Item) => (
                  <CommandItem
                    key={item.value}
                    value={item}
                    onClick={() => setOpen(false)}
                  >
                    {item.label}
                    {item.shortcut && (
                      <CommandShortcut>{item.shortcut}</CommandShortcut>
                    )}
                  </CommandItem>
                )}
              </CommandGroup>
            )}
          </CommandList>
          <CommandEmpty>No results found.</CommandEmpty>
        </Command>
      </CommandDialog>
    </div>
  );
}

API reference

Built from scratch on the Base UI Autocomplete primitive in inline mode — this is SevenUI’s Radix-free replacement for cmdk.

Command

Extends the Autocomplete Root (locked to inline open autoHighlight="always" keepHighlight).

Prop Type Default
items grouped item data passed to render props
filter custom match function (null disables) substring on label
filteredItems externally computed results

CommandDialog

Wraps Command in the Dialog component with sr-only title and description.

Prop Type Default
title string "Command Palette"
description string "Search for a command to run..."
showCloseButton boolean false
className string — on the DialogContent

Data-driven filtering (intentional divergence from cmdk). cmdk filters by reading each item’s rendered DOM text. Command instead filters the items array you pass to the root — CommandList and CommandGroup take a function child ((item) => ReactElement) that Base UI calls once per item currently matching the input value, rather than a plain node. Items use the { value, label } shape; the default filter is a locale-aware substring match against .label. Pass a custom filter function for fuzzy matching, or filter={null} together with a filteredItems prop to drive results from an async search yourself.

Keyboard model. Virtual focus never leaves the input — the highlighted item is tracked via aria-activedescendant, not real DOM focus. Arrow keys move the highlight, Enter activates the highlighted item’s onClick, and inside CommandDialog, Escape closes the dialog (inline Autocomplete mode disables Base UI’s own dismissal so the keypress bubbles up to the Dialog root).

The component ships with unit tests covering rendering, filtering, the empty state, and keyboard activation — see packages/registry/tests/command.test.tsx.

Component Renders
Command Autocomplete.Root (inline open autoHighlight="always" keepHighlight) plus a styled container
CommandInput Autocomplete.Input inside an Input Group with a trailing search-icon addon
CommandList Autocomplete.List — takes a function child, one call per group/item
CommandGroup Autocomplete.Group + Autocomplete.GroupLabel (heading prop) + Autocomplete.Collection
CommandItem Autocomplete.Item — highlighted state via data-highlighted
CommandEmpty Autocomplete.Empty — always mounted as a role="status" live region
CommandShortcut A styled <span> for trailing keybinding hints
CommandSeparator Autocomplete.Separator
CommandDialog The Dialog component wrapping Command, with title / description rendered as sr-only text

Was this page helpful?