# Domain Switch: Command Palette

> Press ⌘K (or the search icon) and a domain opens from the icon in which the page becomes a command palette. Run a command or press Esc and it collapses back into the icon.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/domain-switch-command-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/domain-switch-command-demo
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/keyframeui/domain-switch-command-demo-light-819a148d060c7cab.png
- Registry item: https://keyframeui.com/r/domain-switch-command-demo.json
- Categories: toggle, theme, transition, canvas
- Listed: 2026-10-23, source last changed 2026-10-23

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/domain-switch-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/domain-switch.json`

## Notes from the registry

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

## Files

### `registry/demos/domain-switch-command-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import { DomainSwitch, DomainSwitchScope, DomainSwitchToggle } from "@/components/ui/domain-switch";

type Command = { label: string; group: string; hint?: string };

const commands: Command[] = [
  { label: "Redeploy production", group: "Actions", hint: "⇧R" },
  { label: "Roll back to previous build", group: "Actions" },
  { label: "Invite a teammate", group: "Actions", hint: "I" },
  { label: "Open build logs", group: "Go to", hint: "G L" },
  { label: "Environment variables", group: "Go to", hint: "G E" },
  { label: "Domains", group: "Go to", hint: "G D" },
  { label: "Usage and billing", group: "Go to" },
];

const deployments = [
  { branch: "main", message: "Cache edge responses", age: "4m", state: "Ready" },
  { branch: "feat/search", message: "Fuzzy match on titles", age: "38m", state: "Building" },
  { branch: "main", message: "Bump runtime to 22", age: "2h", state: "Ready" },
  { branch: "fix/og", message: "Fallback social card", age: "5h", state: "Ready" },
  { branch: "main", message: "Drop legacy routes", age: "1d", state: "Ready" },
];

export function DomainSwitchCommandDemo() {
  const [open, setOpen] = React.useState(false);
  const [query, setQuery] = React.useState("");
  const [active, setActive] = React.useState(0);
  const [ran, setRan] = React.useState<string | null>(null);
  const inputRef = React.useRef<HTMLInputElement>(null);

  const results = commands.filter((command) => command.label.toLowerCase().includes(query.trim().toLowerCase()));

  const change = (next: boolean) => {
    setOpen(next);
    if (next) {
      setQuery("");
      setActive(0);
    }
  };

  const run = (command: Command) => {
    setRan(command.label);
    change(false);
  };

  // ⌘K / Ctrl+K opens it from anywhere, Esc puts it away.
  React.useEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key.toLowerCase() === "k" && (event.metaKey || event.ctrlKey)) {
        event.preventDefault();
        setOpen((was) => {
          if (!was) {
            setQuery("");
            setActive(0);
          }
          return !was;
        });
      } else if (event.key === "Escape") {
        setOpen(false);
      }
    };
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
  }, []);

  React.useEffect(() => {
    if (open) inputRef.current?.focus({ preventScroll: true });
  }, [open]);

  return (
    <div className="w-full overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800">
      <DomainSwitch checked={open} onCheckedChange={change} duration={0.95}>
        <DomainSwitchScope
          className="h-[var(--demo-height,520px)]"
          seamClassName="text-[#05060d]"
          outside={
            <div className="flex h-full flex-col bg-white text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100">
              <TopBar>
                <DomainSwitchToggle
                  aria-label="Command palette"
                  className="size-8 rounded-lg text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
                >
                  <SearchIcon />
                </DomainSwitchToggle>
                <kbd className="hidden rounded-md border border-zinc-200 px-1.5 py-0.5 font-sans text-[11px] text-zinc-500 sm:block dark:border-zinc-800">
                  ⌘K
                </kbd>
                <span className="ml-auto size-7 rounded-full bg-gradient-to-br from-amber-200 to-rose-300" />
              </TopBar>
              <div className="min-h-0 flex-1 overflow-hidden px-4 py-5 sm:px-6">
                <div className="flex items-baseline justify-between">
                  <h2 className="text-lg font-semibold tracking-tight">Deployments</h2>
                  <p className="truncate pl-4 text-xs text-zinc-500" aria-live="polite">
                    {ran ? `Ran “${ran}”` : "Press ⌘K for commands"}
                  </p>
                </div>
                <ul className="mt-4 divide-y divide-zinc-100 rounded-xl border border-zinc-200 dark:divide-zinc-900 dark:border-zinc-800">
                  {deployments.map((d) => (
                    <li key={d.message} className="flex items-center gap-3 px-4 py-3 text-sm">
                      <span
                        className={cn(
                          "size-2 shrink-0 rounded-full",
                          d.state === "Ready" ? "bg-emerald-500" : "animate-pulse bg-amber-500",
                        )}
                      />
                      <span className="min-w-0 flex-1 truncate">{d.message}</span>
                      <span className="hidden font-mono text-xs text-zinc-500 sm:block">{d.branch}</span>
                      <span className="w-8 text-right text-xs text-zinc-500">{d.age}</span>
                    </li>
                  ))}
                </ul>
              </div>
            </div>
          }
          inside={
            <div className="flex h-full flex-col bg-[#0b0c16] text-zinc-200">
              <TopBar className="border-white/[0.06]">
                <DomainSwitchToggle
                  aria-label="Command palette"
                  className="size-8 rounded-lg bg-indigo-400/15 text-indigo-200 hover:bg-indigo-400/25"
                >
                  <SearchIcon />
                </DomainSwitchToggle>
                <input
                  ref={inputRef}
                  value={query}
                  onChange={(event) => {
                    setQuery(event.target.value);
                    setActive(0);
                  }}
                  onKeyDown={(event) => {
                    if (event.key === "ArrowDown" || event.key === "ArrowUp") {
                      event.preventDefault();
                      const step = event.key === "ArrowDown" ? 1 : -1;
                      setActive((i) => (results.length ? (i + step + results.length) % results.length : 0));
                    } else if (event.key === "Enter" && results[active]) {
                      event.preventDefault();
                      run(results[active]);
                    }
                  }}
                  placeholder="Type a command or search"
                  aria-label="Command"
                  className="min-w-0 flex-1 bg-transparent text-[16px] text-zinc-100 outline-none placeholder:text-zinc-500 sm:text-sm"
                />
                <kbd className="rounded-md border border-white/10 px-1.5 py-0.5 font-sans text-[11px] text-zinc-400">
                  Esc
                </kbd>
              </TopBar>
              <div className="min-h-0 flex-1 overflow-y-auto px-2 py-3 sm:px-4">
                {results.length === 0 && <p className="px-3 py-6 text-sm text-zinc-500">No commands match.</p>}
                {["Actions", "Go to"].map((group) => {
                  const items = results.filter((command) => command.group === group);
                  if (!items.length) return null;
                  return (
                    <div key={group} className="mb-3">
                      <p className="px-3 pb-1 text-[11px] font-medium tracking-wide text-zinc-500 uppercase">{group}</p>
                      {items.map((command) => {
                        const index = results.indexOf(command);
                        return (
                          <button
                            key={command.label}
                            type="button"
                            onPointerMove={() => setActive(index)}
                            onClick={() => run(command)}
                            className={cn(
                              "flex w-full items-center rounded-lg px-3 py-2.5 text-left text-sm",
                              index === active ? "bg-indigo-400/[0.12] text-white" : "text-zinc-300",
                            )}
                          >
                            {command.label}
                            {command.hint && (
                              <span className="ml-auto font-mono text-[11px] text-zinc-500">{command.hint}</span>
                            )}
                          </button>
                        );
                      })}
                    </div>
                  );
                })}
              </div>
            </div>
          }
        />
      </DomainSwitch>
    </div>
  );
}

function TopBar({ className, children }: { className?: string; children: React.ReactNode }) {
  return (
    <div
      className={cn(
        "flex h-14 shrink-0 items-center gap-2 border-b border-zinc-200 px-3 sm:px-4 dark:border-zinc-800",
        className,
      )}
    >
      <span className="mr-2 flex items-center gap-2 text-sm font-semibold tracking-tight">
        <span className="size-4 rotate-45 rounded-[3px] bg-current" />
        <span className="hidden sm:inline">Relay</span>
      </span>
      {children}
    </div>
  );
}

function SearchIcon() {
  return (
    <svg viewBox="0 0 16 16" className="size-4" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" aria-hidden>
      <circle cx="7" cy="7" r="4.5" />
      <path d="m10.5 10.5 3 3" />
    </svg>
  );
}
```
