# Manifest — Animated UI Component for React

> A ⌘K command palette: a spring-seated glass search box over a scrim with a fuzzy-filtered, keyboard-driven list of grouped commands and pages. A shared-layout pill slides between rows, matched substrings ignite to full contrast, and rows with children push a nested page in from the right. Full combobox a11y, focus trap, ⌘K/Ctrl-K toggle, and controlled open.

- Page: https://crucibleui.com/components/utility/manifest
- Category: [utility](https://crucibleui.com/components/utility)
- Tier: pro (one-time purchase, see https://crucibleui.com/pricing)
- Engine: motion
- npm dependencies: motion

## Install

```bash
npx shadcn@latest add @crucible/manifest
```

Pro install requires adding the token-bearing `@crucible` registry to components.json (issued at https://crucibleui.com/account after purchase).

Import from `@/components/crucible/manifest`.

## Props (`ManifestProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `ManifestItem[]` | — | Root command/page list. |
| `open?` | `boolean` | — | Controlled open state. Pair with `onOpenChange`. |
| `defaultOpen?` | `boolean` | `false` | Initial open state when uncontrolled. |
| `onOpenChange?` | `(open: boolean) => void` | — | Fires whenever the palette opens or closes, controlled or not. |
| `trigger?` | `React.ReactNode` | — | Element that opens the palette on click (uncontrolled convenience). Controlled callers can drive `open` and omit this. |
| `hotkey?` | `string \| false` | `"k"` | Single key that, with ⌘ (mac) or Ctrl, toggles the palette. Set `false` to disable the global hotkey. |
| `placeholder?` | `string` | `"Type a command or search…"` | Search input placeholder. |
| `emptyMessage?` | `React.ReactNode` | `"No results"` | Shown when nothing matches. |
| `label?` | `string` | `"Command palette"` | Accessible label for the dialog. |
| `footer?` | `React.ReactNode` | — | Footer hint row content. Pass `null` to hide it. |
| `maxVisible?` | `number` | `50` | Hard cap on rendered rows per page — long lists are sliced. |
| `container?` | `HTMLElement \| null` | ``document.body` (fixed, full-viewport, scroll-locked). Pass a stage element for previews` | Portal target (must be a positioned element). |

Also accepts: `Omit<React.ComponentPropsWithoutRef<"div">, "onSelect">`.

## Usage example

```tsx
"use client";

import * as React from "react";

import { Manifest, type ManifestItem } from "@/components/crucible/manifest";

function Dot() {
  return (
    <svg aria-hidden viewBox="0 0 24 24" fill="none" className="size-full">
      <circle cx="12" cy="12" r="6" stroke="currentColor" strokeWidth="1.75" />
    </svg>
  );
}

export default function ManifestDemo() {
  const stageRef = React.useRef<HTMLDivElement>(null);
  const [container, setContainer] = React.useState<HTMLElement | null>(null);
  const [open, setOpen] = React.useState(false);
  const [last, setLast] = React.useState<string | null>(null);

  React.useEffect(() => setContainer(stageRef.current), []);

  const items: ManifestItem[] = React.useMemo(() => {
    const run = (name: string) => () => setLast(name);
    return [
      {
        id: "new-file",
        label: "New file",
        group: "Actions",
        keywords: ["create", "add"],
        shortcut: ["⌘", "N"],
        icon: <Dot />,
        onSelect: run("New file"),
      },
      {
        id: "search",
        label: "Search everything",
        group: "Actions",
        keywords: ["find", "lookup"],
        shortcut: ["⌘", "F"],
        icon: <Dot />,
        onSelect: run("Search everything"),
      },
      {
        id: "copy-link",
        label: "Copy link",
        group: "Actions",
        keywords: ["url", "share"],
        icon: <Dot />,
        onSelect: run("Copy link"),
      },
      {
        id: "go-to",
        label: "Go to…",
        group: "Pages",
        icon: <Dot />,
        keywords: ["navigate", "jump"],
        children: [
          {
            id: "home",
            label: "Home",
            keywords: ["dashboard"],
            onSelect: run("Home"),
          },
          { id: "projects", label: "Projects", onSelect: run("Projects") },
          { id: "settings", label: "Settings", onSelect: run("Settings") },
          { id: "billing", label: "Billing", onSelect: run("Billing") },
        ],
      },
      {
        id: "theme",
        label: "Change theme…",
        group: "Pages",
        icon: <Dot />,
        keywords: ["appearance", "dark", "light"],
        children: [
          { id: "system", label: "System", onSelect: run("Theme: System") },
          { id: "dark", label: "Dark", onSelect: run("Theme: Dark") },
          { id: "light", label: "Light", onSelect: run("Theme: Light") },
        ],
      },
      {
        id: "invite",
        label: "Invite teammate",
        group: "Team",
        icon: <Dot />,
        keywords: ["member", "add user"],
        onSelect: run("Invite teammate"),
      },
      {
        id: "archived",
        label: "Archived (unavailable)",
        group: "Team",
        icon: <Dot />,
        disabled: true,
      },
    ];
  }, []);

  return (
    <div
      ref={stageRef}
      className="relative isolate flex h-full min-h-[360px] w-full flex-col items-center justify-center gap-4 overflow-hidden rounded-xl bg-neutral-950"
    >
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_50%_0%,rgba(255,255,255,0.04),transparent_70%)]"
      />

      <button
        type="button"
        onClick={() => setOpen(true)}
        className="relative z-10 flex items-center gap-2 rounded-lg border border-white/15 bg-white/[0.06] px-4 py-2 text-sm font-medium text-neutral-100 shadow-[inset_0_1px_0_rgba(255,255,255,0.1)] transition-colors hover:bg-white/[0.1] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50"
      >
        Open palette
        <kbd className="flex items-center gap-0.5 rounded border border-sky-300/20 bg-sky-300/[0.06] px-1.5 py-0.5 font-sans text-[11px] font-medium text-sky-200/80">
          ⌘K
        </kbd>
      </button>

      <p className="relative z-10 text-sm text-neutral-500">
        Press{" "}
        <kbd className="rounded border border-white/10 bg-white/[0.04] px-1 text-[11px] text-neutral-400">
          ⌘K
        </kbd>{" "}
        · type to filter · arrows + enter · drill into a page, then Esc to step
        back.
      </p>

      {last && (
        <p className="relative z-10 text-xs text-neutral-600">
          Last run: <span className="text-neutral-400">{last}</span>
        </p>
      )}

      <Manifest
        items={items}
        open={open}
        onOpenChange={setOpen}
        container={container}
        label="Demo command palette"
      />
    </div>
  );
}
```

## Accessibility & performance

Honors `prefers-reduced-motion` with a designed static frame or fade (never a blank box) and passes `className` through.
