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.
registry/demos/domain-switch-command-demo.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>
);
}
Live preview, examples and props: https://keyframeui.com/components/domain-switch#domain-switch-command-demo