# Morph Search: Command Menu

> An app's command menu. The trigger travels to the middle of the screen, with grouped commands and their shortcuts. Opens with ⌘K.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/morph-search-command-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/morph-search-command-demo
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/keyframeui/morph-search-command-demo-light-4e38efd24777eab0.png
- Registry item: https://keyframeui.com/r/morph-search-command-demo.json
- Categories: search, command-menu, motion
- Listed: 2026-10-14, source last changed 2026-10-15

## Install

```bash
npx shadcn@latest add https://keyframeui.com/r/morph-search-command-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/morph-search.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/morph-search#morph-search-command-demo

## Files

### `registry/demos/morph-search-command-demo.tsx`

```tsx
"use client";

import * as React from "react";
import { AnimatePresence, motion } from "motion/react";

import { MorphSearch, MorphSearchIcon, type MorphSearchItem } from "@/components/ui/morph-search";

const glyph = (d: string) => (
  <svg aria-hidden viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75} strokeLinecap="round" strokeLinejoin="round">
    <path d={d} />
  </svg>
);

const useIsMac = () =>
  React.useSyncExternalStore(
    () => () => {},
    () => /Mac|iPhone|iPad/.test(navigator.platform),
    () => true,
  );

function commands(mod: string): MorphSearchItem[] {
  return [
    { id: "new-project", group: "Suggestions", label: "Create new project", icon: glyph("M12 5v14M5 12h14"), shortcut: [mod, "N"] },
    { id: "invite", group: "Suggestions", label: "Invite teammate", icon: glyph("M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8M19 8v6M22 11h-6"), shortcut: [mod, "I"] },
    { id: "deploy", group: "Suggestions", label: "Deploy to production", icon: glyph("M13 2 3 14h9l-1 8 10-12h-9l1-8z"), keywords: ["ship", "release"] },
    { id: "dashboard", group: "Go to", label: "Dashboard", icon: glyph("M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z"), shortcut: ["G", "D"] },
    { id: "projects", group: "Go to", label: "Projects", icon: glyph("M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"), shortcut: ["G", "P"] },
    { id: "billing", group: "Go to", label: "Billing", icon: glyph("M2 6h20v12H2zM2 10h20M6 15h4"), shortcut: ["G", "B"], keywords: ["invoices", "plan"] },
    { id: "settings", group: "Go to", label: "Settings", icon: glyph("M4 6h9M17 6h3M4 12h3M11 12h9M4 18h11M19 18h1M15 4v4M9 10v4M17 16v4"), shortcut: [mod, ","], keywords: ["preferences"] },
    { id: "light", group: "Theme", label: "Light", icon: glyph("M12 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"), keywords: ["appearance"] },
    { id: "dark", group: "Theme", label: "Dark", icon: glyph("M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"), keywords: ["appearance", "night"] },
    { id: "system", group: "Theme", label: "Match system", icon: glyph("M3 4h18v12H3zM8 20h8M12 16v4"), keywords: ["appearance", "auto"] },
    { id: "log-out", group: "Account", label: "Log out", icon: glyph("M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"), keywords: ["sign out"] },
  ];
}

export function MorphSearchCommandDemo() {
  const mod = useIsMac() ? "⌘" : "Ctrl";
  const items = React.useMemo(() => commands(mod), [mod]);
  const [ran, setRan] = React.useState<{ label: string; at: number } | null>(null);

  React.useEffect(() => {
    if (!ran) return;
    const timer = window.setTimeout(() => setRan(null), 2400);
    return () => window.clearTimeout(timer);
  }, [ran]);

  return (
    <div className="relative flex h-[26rem] w-full max-w-3xl overflow-hidden rounded-xl border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-950">
      <aside className="hidden w-48 shrink-0 flex-col gap-1 border-r border-zinc-200/80 p-3 sm:flex dark:border-zinc-800/80">
        <span className="mb-3 flex items-center gap-2 px-2 text-sm font-semibold text-zinc-900 dark:text-zinc-50">
          <span aria-hidden className="size-5 rounded-md bg-orange-500" />
          Acme
        </span>
        {["Dashboard", "Projects", "Deployments", "Billing", "Settings"].map((link, i) => (
          <span
            key={link}
            className={
              i === 1
                ? "rounded-md bg-zinc-100 px-2 py-1.5 text-sm text-zinc-900 dark:bg-zinc-900 dark:text-zinc-50"
                : "px-2 py-1.5 text-sm text-zinc-500"
            }
          >
            {link}
          </span>
        ))}
      </aside>

      <div className="flex min-w-0 flex-1 flex-col">
        <header className="flex h-14 items-center justify-between gap-4 border-b border-zinc-200/80 px-4 dark:border-zinc-800/80">
          <p className="truncate text-sm text-zinc-500">
            Acme <span className="px-1 text-zinc-300 dark:text-zinc-700">/</span>
            <span className="text-zinc-900 dark:text-zinc-50">Projects</span>
          </p>
          <MorphSearch
            variant="palette"
            hotkey="k"
            items={items}
            placeholder="Type a command or search…"
            onSelect={(item) => setRan({ label: item.label, at: Date.now() })}
            className="h-8 gap-2 rounded-lg px-2 text-[13px]"
          >
            <MorphSearchIcon className="size-3.5" />
            <span>Commands</span>
            <kbd className="ml-1 rounded-[5px] bg-foreground/[0.06] px-1.5 py-0.5 font-mono text-[10.5px]">
              {mod === "⌘" ? "⌘K" : "Ctrl K"}
            </kbd>
          </MorphSearch>
        </header>

        <div className="grid flex-1 grid-cols-2 content-start gap-3 p-4 sm:grid-cols-3">
          {["Marketing site", "Mobile app", "Docs", "Billing service", "Design system", "Status page"].map((name) => (
            <div key={name} className="rounded-lg border border-zinc-200/80 p-3 dark:border-zinc-800/80">
              <span aria-hidden className="block size-6 rounded-md bg-zinc-100 dark:bg-zinc-900" />
              <p className="mt-6 truncate text-sm font-medium text-zinc-900 dark:text-zinc-50">{name}</p>
              <p className="text-xs text-zinc-500">Updated today</p>
            </div>
          ))}
        </div>
      </div>

      <AnimatePresence>
        {ran ? (
          <motion.p
            key={ran.at}
            role="status"
            initial={{ opacity: 0, y: 8 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: 4 }}
            transition={{ duration: 0.2, ease: "easeOut" }}
            className="absolute bottom-4 left-1/2 flex -translate-x-1/2 items-center gap-2 rounded-full bg-zinc-900 px-3.5 py-2 text-xs text-zinc-50 shadow-lg dark:bg-zinc-50 dark:text-zinc-900"
          >
            <svg aria-hidden viewBox="0 0 24 24" className="size-3.5" fill="none" stroke="currentColor" strokeWidth={2.25} strokeLinecap="round" strokeLinejoin="round">
              <path d="M5 12.5l4.5 4.5L19 7.5" />
            </svg>
            {ran.label}
          </motion.p>
        ) : null}
      </AnimatePresence>
    </div>
  );
}
```
