Skip to main content

Commander

A spotlight-style command palette with fuzzy search, grouped commands, and keyboard-driven navigation. Press Alt+K from anywhere to invoke it.

Package@g4rcez/componentsSectionFloating Elements

Examples

Basic Usage

Fuzzy search across multiple command groups. Try searching for 'log', 'play', or 'term' — or press Alt+K anywhere on this page.

Or press Alt + K

tsx
import { CommandPalette, Button } from "@g4rcez/components";
import {
  SquaresFourIcon, FolderIcon, GearIcon,
  TerminalIcon, FileTextIcon, PlayIcon,
  HouseIcon, MagnifyingGlassIcon, SignOutIcon,
} from "@phosphor-icons/react";
import { useState } from "react";

function CommanderExample() {
  const [open, setOpen] = useState(false);

  const commands = [
    {
      type: "group",
      title: "Navigation",
      items: [
        { type: "shortcut", title: "Dashboard", shortcut: "Alt+d", Icon: <SquaresFourIcon size={16} />, action: () => setOpen(false) },
        { type: "shortcut", title: "Projects",  shortcut: "Alt+p", Icon: <FolderIcon size={16} />,           action: () => setOpen(false) },
        { type: "shortcut", title: "Settings",  shortcut: "Alt+s", Icon: <GearIcon size={16} />,         action: () => setOpen(false) },
      ],
    },
    {
      type: "group",
      title: "Developer",
      items: [
        { type: "shortcut", title: "Console",        shortcut: "Alt+t", Icon: <TerminalIcon size={16} />,  action: () => setOpen(false) },
        { type: "shortcut", title: "Logs",           shortcut: "Alt+g", Icon: <FileTextIcon size={16} />, action: () => setOpen(false) },
        { type: "shortcut", title: "API Playground", shortcut: "Alt+a", Icon: <PlayIcon size={16} />,      action: () => setOpen(false) },
      ],
    },
    {
      type: "group",
      title: "Account",
      items: [
        { type: "shortcut", title: "Logout", shortcut: "Alt+l", Icon: <SignOutIcon size={16} />, action: () => setOpen(false) },
      ],
    },
  ];

  return (
    <>
      <Button theme="primary" onClick={() => setOpen(true)}>Open Commander</Button>
      <CommandPalette open={open} bind="Alt+k" commands={commands} onChangeVisibility={setOpen} />
    </>
  );
}

With Empty State

When a search query matches nothing, a custom empty message is displayed. Try opening the palette and typing something like 'xyz'.

Type xyz to see the empty state

tsx
import { CommandPalette, Button } from "@g4rcez/components";
import { HouseIcon, MagnifyingGlassIcon } from "@phosphor-icons/react";
import { useState } from "react";

function EmptyStateExample() {
  const [open, setOpen] = useState(false);

  const commands = [
    {
      type: "group",
      title: "General",
      items: [
        { type: "shortcut", title: "Go to Home",   shortcut: "Alt+h", Icon: <HouseIcon size={16} />,   action: () => setOpen(false) },
        { type: "shortcut", title: "Quick Search", shortcut: "Alt+f", Icon: <MagnifyingGlassIcon size={16} />, action: () => setOpen(false) },
      ],
    },
  ];

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open Commander</Button>
      <CommandPalette
        open={open}
        commands={commands}
        onChangeVisibility={setOpen}
        emptyMessage="No commands match your search. Try a different term."
      />
    </>
  );
}

With Preview Panel

The first available command appears in the page preview on open. Use the arrow keys to explore its cover, details, and database; the Projects filter is consumer-owned.

Use ↑ ↓ arrow keys to update the preview, or use the collapsible Projects filter.

tsx
import { CommandPalette, Button } from "@g4rcez/components";
import type React from "react";
import type { CommandItemTypes } from "@g4rcez/components";
import { SquaresFourIcon, FolderIcon, GearIcon } from "@phosphor-icons/react";
import { useState } from "react";

const previewRows = [
  { title: "Command palette", status: "In progress" },
  { title: "Project filters", status: "In review" },
  { title: "Keyboard shortcuts", status: "Ready" },
] as const;

const CommandPreview: React.FC<{ command: CommandItemTypes; text: string }> = ({ command, text }) => {
  if (command.type !== "shortcut") return null;
  const Title = command.title;
  const title = typeof Title === "function" ? <Title text={text} /> : Title;
  const hint = Array.isArray(command.hint) ? command.hint.join(" · ") : command.hint;

  return (
    <article className="flex min-w-0 flex-col gap-base">
      <div className="flex min-h-24 items-end justify-between gap-base overflow-hidden rounded-card-radius bg-muted p-base">
        <div className="min-w-0">
          <p className="text-typography-xs font-semibold text-foreground">Workspace</p>
          <p className="mt-1 text-typography-sm text-foreground/80">A page in your team space</p>
        </div>
        <FolderIcon aria-hidden="true" className="shrink-0 text-primary/20" size={64} />
      </div>
      <header className="flex min-w-0 items-start gap-sm">
        <div aria-hidden="true" className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
          {command.Icon ?? <FolderIcon size={20} />}
        </div>
        <div className="min-w-0 flex-1">
          <p className="text-typography-xs font-medium text-muted-foreground">Workspace page</p>
          <h2 className="mt-1 break-words text-typography-xl font-semibold text-foreground">{title}</h2>
        </div>
        {command.shortcut && (
          <kbd className="inline-flex shrink-0 items-center rounded-tag-radius bg-muted px-tag-padding-x py-tag-padding-y font-mono text-typography-xs text-muted-foreground">
            {command.shortcut}
          </kbd>
        )}
      </header>
      {hint && <p className="text-typography-sm leading-relaxed text-muted-foreground">{hint}</p>}
      <section aria-label="Recently updated workspace pages" className="min-w-0 overflow-hidden rounded-card-radius border border-floating-border bg-floating-background">
        <div className="flex items-center justify-between gap-sm border-b border-floating-border px-base py-sm">
          <h3 className="text-typography-sm font-semibold text-foreground">Recent work</h3>
          <span className="text-typography-xs text-muted-foreground">Example data</span>
        </div>
        <table className="w-full table-fixed border-collapse text-left text-typography-xs">
          <caption className="sr-only">Example workspace pages and their status</caption>
          <thead>
            <tr className="border-b border-floating-border text-muted-foreground">
              <th scope="col" className="w-2/3 px-base py-sm font-medium">Page</th>
              <th scope="col" className="px-base py-sm font-medium">Status</th>
            </tr>
          </thead>
          <tbody>
            {previewRows.map((row) => (
              <tr className="border-b border-floating-border last:border-0" key={row.title}>
                <td className="truncate px-base py-sm font-medium text-foreground">{row.title}</td>
                <td className="px-base py-sm">
                  <span className="inline-flex max-w-full truncate rounded-tag-radius bg-muted px-tag-padding-x py-tag-padding-y text-foreground">
                    {row.status}
                  </span>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </section>
    </article>
  );
};

function PreviewExample() {
  const [open, setOpen] = useState(false);
  const [projectsOnly, setProjectsOnly] = useState(false);
  const allCommands: CommandItemTypes[] = [
    { type: "shortcut", title: "Dashboard", shortcut: "Alt+d", Icon: <SquaresFourIcon size={20} />, hint: "Navigate to your personal dashboard.", action: () => setOpen(false) },
    { type: "shortcut", title: "Projects", shortcut: "Alt+p", Icon: <FolderIcon size={20} />, hint: "Browse and manage your projects.", action: () => setOpen(false) },
    { type: "shortcut", title: "Settings", shortcut: "Alt+s", Icon: <GearIcon size={20} />, hint: "Configure your account preferences.", action: () => setOpen(false) },
  ];
  const commands = projectsOnly ? allCommands.filter((command) => command.type === "shortcut" && command.title === "Projects") : allCommands;

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open Commander</Button>
      <CommandPalette
        open={open}
        commands={commands}
        onChangeVisibility={setOpen}
        filters={
          <button
            type="button"
            aria-pressed={projectsOnly}
            className={`rounded border border-floating-border px-3 py-1.5 text-sm ${projectsOnly ? "bg-primary/10 text-primary" : "text-foreground hover:bg-muted"}`}
            onClick={() => setProjectsOnly((enabled) => !enabled)}
          >
            Projects only
          </button>
        }
        Preview={CommandPreview}
      />
    </>
  );
}

When to use Commander

Use when

You need the documented Commander interaction or presentation in a product workflow.

Avoid when

A native HTML element already provides the Commander behavior you need.

Accessibility

Keep the Commander labeled and use its documented keyboard interaction. The component preserves visible focus and communicates state without relying on color alone.

  • Keep keyboard focus visible and follow the documented key commands.
  • Provide an accessible name and connect descriptions or errors to the control.