Command
A command palette component built with Dialog and Autocomplete for searching and executing commands. Built with Base UI and Tailwind CSS. Copy-paste ready.
"use client";
import { ArrowDownIcon, ArrowUpIcon, CornerDownLeftIcon } from "lucide-react";
import { Fragment, useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandFooter,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
export interface Item {
value: string;
label: string;
shortcut?: string;
}
export interface Group {
value: string;
items: Item[];
}
export const suggestions: Item[] = [
{ label: "Linear", shortcut: "⌘L", value: "linear" },
{ label: "Figma", shortcut: "⌘F", value: "figma" },
{ label: "Slack", shortcut: "⌘S", value: "slack" },
{ label: "YouTube", shortcut: "⌘Y", value: "youtube" },
{ label: "Raycast", shortcut: "⌘R", value: "raycast" },
];
export const commands: Item[] = [
{ label: "Clipboard History", shortcut: "⌘⇧C", value: "clipboard-history" },
{ label: "Import Extension", shortcut: "⌘I", value: "import-extension" },
{ label: "Create Snippet", shortcut: "⌘N", value: "create-snippet" },
{ label: "System Preferences", shortcut: "⌘,", value: "system-preferences" },
{ label: "Window Management", shortcut: "⌘⇧W", value: "window-management" },
];
export const groupedItems: Group[] = [
{ items: suggestions, value: "Suggestions" },
{ items: commands, value: "Commands" },
];
export default function Particle() {
const [open, setOpen] = useState(false);
function handleItemClick(_item: Item) {
setOpen(false);
}
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Open Command Palette
<KbdGroup>
<Kbd>⌘</Kbd>
<Kbd>J</Kbd>
</KbdGroup>
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={groupedItems}>
<CommandInput placeholder="Search for apps and commands..." />
<CommandPanel>
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(group: Group, _index: number) => (
<Fragment key={group.value}>
<CommandGroup items={group.items}>
<CommandGroupLabel>{group.value}</CommandGroupLabel>
<CommandCollection>
{(item: Item) => (
<CommandItem
key={item.value}
onClick={() => handleItemClick(item)}
value={item.value}
>
<span className="flex-1">{item.label}</span>
{item.shortcut && (
<CommandShortcut>{item.shortcut}</CommandShortcut>
)}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
<CommandSeparator />
</Fragment>
)}
</CommandList>
</CommandPanel>
<CommandFooter>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<KbdGroup>
<Kbd>
<ArrowUpIcon />
</Kbd>
<Kbd>
<ArrowDownIcon />
</Kbd>
</KbdGroup>
<span>Navigate</span>
</div>
<div className="flex items-center gap-2">
<Kbd>
<CornerDownLeftIcon />
</Kbd>
<span>Open</span>
</div>
</div>
<div className="flex items-center gap-2">
<Kbd>Esc</Kbd>
<span>Close</span>
</div>
</CommandFooter>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/command
Usage
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandFooter,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command"
import { Button } from "@/components/ui/button"const items = [
{ value: "linear", label: "Linear" },
{ value: "figma", label: "Figma" },
{ value: "slack", label: "Slack" },
]
<CommandDialog>
<CommandDialogTrigger render={<Button variant="outline" />}>
Open Command Palette
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={items}>
<CommandInput placeholder="Search..." />
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(item) => (
<CommandItem key={item.value} value={item.value}>
{item.label}
</CommandItem>
)}
</CommandList>
</Command>
</CommandDialogPopup>
</CommandDialog>Examples
Simple Command
A basic command dialog with a flat list of searchable items and an empty-state message when no results match.
"use client";
import { CreditCardIcon, SettingsIcon, UserIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandGroup,
CommandItem,
CommandList,
CommandShortcut,
} from "@/components/ui/command";
export default function Component() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Simple Command
</CommandDialogTrigger>
<CommandDialogPopup className="max-w-xs">
<Command>
<CommandList>
<CommandGroup>
<CommandItem className="gap-2">
<UserIcon className="size-4 shrink-0" />
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem className="gap-2">
<CreditCardIcon className="size-4 shrink-0" />
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem className="gap-2">
<SettingsIcon className="size-4 shrink-0" />
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
With Groups
Organizes items into labeled sections (e.g. "Settings", "Shortcuts") using CommandGroup for better command palette discoverability.
"use client";
import {
CalculatorIcon,
CalendarIcon,
CreditCardIcon,
SettingsIcon,
SmileIcon,
UserIcon,
} from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandGroup,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
export default function Component() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Open Command
</CommandDialogTrigger>
<CommandDialogPopup className="max-w-sm">
<Command>
<CommandList>
<CommandGroup>
<CommandItem className="gap-2">
<CalendarIcon className="size-4 shrink-0" />
<span>Calendar</span>
</CommandItem>
<CommandItem className="gap-2">
<SmileIcon className="size-4 shrink-0" />
<span>Search Emoji</span>
</CommandItem>
<CommandItem className="gap-2">
<CalculatorIcon className="size-4 shrink-0" />
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup>
<CommandItem className="gap-2">
<UserIcon className="size-4 shrink-0" />
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem className="gap-2">
<CreditCardIcon className="size-4 shrink-0" />
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem className="gap-2">
<SettingsIcon className="size-4 shrink-0" />
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
File Search
Filters a list of files by name as the user types, showing the file path and type for each match.
"use client";
import { FileIcon, SearchIcon } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
} from "@/components/ui/command";
import { Kbd } from "@/components/ui/kbd";
const files = [
{ name: "page.tsx", path: "src/app/page.tsx", type: "tsx" },
{ name: "layout.tsx", path: "src/app/layout.tsx", type: "tsx" },
{ name: "globals.css", path: "src/styles/globals.css", type: "css" },
{ name: "utils.ts", path: "src/lib/utils.ts", type: "ts" },
{ name: "api.ts", path: "src/lib/api.ts", type: "ts" },
{ name: "button.tsx", path: "src/components/ui/button.tsx", type: "tsx" },
{ name: "package.json", path: "package.json", type: "json" },
{ name: "README.md", path: "README.md", type: "md" },
];
type File = (typeof files)[number];
export default function Component() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger
render={<Button className="w-52" variant="outline" />}
>
<SearchIcon className="size-4 shrink-0" />
Search files...
<Kbd className="ml-auto">⌘K</Kbd>
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={files}>
<CommandInput placeholder="Search files by name..." />
<CommandPanel>
<CommandEmpty>No files found.</CommandEmpty>
<CommandList>
{(file: File) => (
<CommandItem
className="gap-2"
key={file.path}
value={file.name}
>
<FileIcon className="size-4 shrink-0" />
<div className="flex flex-1 items-center gap-2 overflow-hidden">
<span className="font-medium">{file.name}</span>
<span className="truncate text-muted-foreground text-xs">
{file.path}
</span>
</div>
<div className="ml-auto" data-slot="command-shortcut">
<Badge size="sm" variant="outline">
{file.type}
</Badge>
</div>
</CommandItem>
)}
</CommandList>
</CommandPanel>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
User Search
Searches a user directory and displays each match with an avatar, name, and role details.
"use client";
import { useState } from "react";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
} from "@/components/ui/command";
const users = [
{
avatar:
"https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=96&h=96&dpr=2&q=80",
email: "alex@example.com",
name: "Alex Johnson",
role: "Admin",
},
{
avatar:
"https://images.unsplash.com/photo-1519699047748-de8e457a634e?w=96&h=96&dpr=2&q=80",
email: "sarah@example.com",
name: "Sarah Chen",
role: "Editor",
},
{
avatar:
"https://images.unsplash.com/photo-1607990281513-2c110a25bd8c?w=96&h=96&dpr=2&q=80",
email: "david@example.com",
name: "David Kim",
role: "Viewer",
},
{
avatar:
"https://images.unsplash.com/photo-1485893086445-ed75865251e0?w=96&h=96&dpr=2&q=80",
email: "emma@example.com",
name: "Emma Wilson",
role: "Admin",
},
{
avatar:
"https://images.unsplash.com/photo-1584308972272-9e4e7685e80f?w=96&h=96&dpr=2&q=80",
email: "michael@example.com",
name: "Michael Rodriguez",
role: "Editor",
},
];
type User = (typeof users)[number];
export default function Component() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Search Users
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={users}>
<CommandInput placeholder="Search by name or email..." />
<CommandPanel>
<CommandEmpty>No users found.</CommandEmpty>
<CommandList>
{(user: User) => (
<CommandItem
className="gap-2 py-2"
key={user.email}
value={user.name}
>
<Avatar className="size-6 shrink-0">
<AvatarImage alt={user.name} src={user.avatar} />
<AvatarFallback className="text-xs">
{user.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="flex flex-1 flex-col">
<span className="font-medium text-sm">{user.name}</span>
<span className="text-muted-foreground text-xs">
{user.email}
</span>
</div>
<div className="ml-auto" data-slot="command-shortcut">
<Badge
size="sm"
variant={
user.role === "Admin"
? "default"
: user.role === "Editor"
? "info"
: "success"
}
>
{user.role}
</Badge>
</div>
</CommandItem>
)}
</CommandList>
</CommandPanel>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
With Shortcuts
Lists available actions alongside their keyboard shortcuts using CommandShortcut, similar to a VS Code command palette.
"use client";
import {
FileTextIcon,
HouseIcon,
InboxIcon,
LogOutIcon,
MoonIcon,
PlusIcon,
UserPlusIcon,
} from "lucide-react";
import { Fragment, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
type Item = {
value: string;
label: string;
shortcut: string;
icon:
| "plus"
| "file-text"
| "user-plus"
| "house"
| "inbox"
| "moon"
| "log-out";
};
type Group = { value: string; label: string; items: Item[] };
const groups: Group[] = [
{
items: [
{
icon: "plus",
label: "New Project",
shortcut: "⌘N",
value: "new-project",
},
{
icon: "file-text",
label: "New Document",
shortcut: "⌘⇧N",
value: "new-document",
},
{
icon: "user-plus",
label: "Invite Member",
shortcut: "⌘I",
value: "invite-member",
},
],
label: "Create",
value: "create",
},
{
items: [
{
icon: "house",
label: "Go to Dashboard",
shortcut: "⌘D",
value: "go-to-dashboard",
},
{
icon: "inbox",
label: "Go to Inbox",
shortcut: "⌘⇧I",
value: "go-to-inbox",
},
],
label: "Navigate",
value: "navigate",
},
{
items: [
{
icon: "moon",
label: "Toggle Dark Mode",
shortcut: "⌘⇧D",
value: "toggle-dark-mode",
},
{ icon: "log-out", label: "Sign Out", shortcut: "⌘Q", value: "sign-out" },
],
label: "Settings",
value: "settings",
},
];
const icons: Record<Item["icon"], React.ReactNode> = {
"file-text": <FileTextIcon className="size-4 shrink-0" />,
house: <HouseIcon className="size-4 shrink-0" />,
inbox: <InboxIcon className="size-4 shrink-0" />,
"log-out": <LogOutIcon className="size-4 shrink-0" />,
moon: <MoonIcon className="size-4 shrink-0" />,
plus: <PlusIcon className="size-4 shrink-0" />,
"user-plus": <UserPlusIcon className="size-4 shrink-0" />,
};
export default function Component() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Quick Actions
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={groups}>
<CommandInput placeholder="What do you need?" />
<CommandPanel>
<CommandEmpty>No actions found.</CommandEmpty>
<CommandList>
{(group: Group, index: number) => (
<Fragment key={group.value}>
{index > 0 && <CommandSeparator />}
<CommandGroup items={group.items}>
<CommandGroupLabel>{group.label}</CommandGroupLabel>
<CommandCollection>
{(item: Item) => (
<CommandItem
className="gap-2"
key={item.value}
value={item.value}
>
{icons[item.icon]}
<span>{item.label}</span>
<CommandShortcut>{item.shortcut}</CommandShortcut>
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
</Fragment>
)}
</CommandList>
</CommandPanel>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
Recent and Favorites
Pre-populates the command list with recently used and starred items before the user types a search query.
"use client";
import {
BookOpenIcon,
ClockIcon,
LifeBuoyIcon,
MessageSquareIcon,
StarIcon,
} from "lucide-react";
import { Fragment, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
} from "@/components/ui/command";
type Item = {
value: string;
label: string;
time?: string;
icon: "star" | "clock" | "book" | "lifebuoy" | "message";
};
type Group = { value: string; label: string; items: Item[] };
const groups: Group[] = [
{
items: [
{ icon: "star", label: "Design System", value: "design-system" },
{ icon: "star", label: "API Documentation", value: "api-documentation" },
],
label: "Starred",
value: "starred",
},
{
items: [
{
icon: "clock",
label: "Dashboard Analytics",
time: "2m ago",
value: "dashboard-analytics",
},
{
icon: "clock",
label: "User Settings",
time: "15m ago",
value: "user-settings",
},
{
icon: "clock",
label: "Team Members",
time: "1h ago",
value: "team-members",
},
{
icon: "clock",
label: "Billing & Plans",
time: "2h ago",
value: "billing-plans",
},
],
label: "Recent",
value: "recent",
},
{
items: [
{ icon: "book", label: "Documentation", value: "documentation" },
{ icon: "lifebuoy", label: "Help & Support", value: "help-support" },
{ icon: "message", label: "Contact Us", value: "contact-us" },
],
label: "Help",
value: "help",
},
];
const icons: Record<Item["icon"], React.ReactNode> = {
book: <BookOpenIcon className="size-4 shrink-0" />,
clock: <ClockIcon className="size-4 shrink-0 text-muted-foreground" />,
lifebuoy: <LifeBuoyIcon className="size-4 shrink-0" />,
message: <MessageSquareIcon className="size-4 shrink-0" />,
star: <StarIcon className="size-4 shrink-0 text-yellow-500" />,
};
export default function Component() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Search Everything
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={groups}>
<CommandInput placeholder="Search or jump to..." />
<CommandPanel>
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(group: Group, index: number) => (
<Fragment key={group.value}>
{index > 0 && <CommandSeparator />}
<CommandGroup items={group.items}>
<CommandGroupLabel>{group.label}</CommandGroupLabel>
<CommandCollection>
{(item: Item) => (
<CommandItem
className="gap-2"
key={item.value}
value={item.value}
>
{icons[item.icon]}
<span>{item.label}</span>
{item.time && (
<div
className="ml-auto"
data-slot="command-shortcut"
>
<span>{item.time}</span>
</div>
)}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
</Fragment>
)}
</CommandList>
</CommandPanel>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
Account Settings Palette
Three groups — Account, Preferences, and Session — each with icon-prefixed items. Sign out is in the Session group styled as destructive.
"use client";
import {
BarChart2Icon,
BellIcon,
CreditCardIcon,
KeyIcon,
LogOutIcon,
MoonIcon,
SunIcon,
UserIcon,
} from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
} from "@/components/ui/command";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
type Action = { icon: React.ReactNode; label: string; value: string };
type Section = { items: Action[]; label: string; value: string };
const sections: Section[] = [
{
items: [
{
icon: <UserIcon className="size-4" />,
label: "Edit profile",
value: "profile",
},
{
icon: <BellIcon className="size-4" />,
label: "Notifications",
value: "notifications",
},
{
icon: <CreditCardIcon className="size-4" />,
label: "Billing",
value: "billing",
},
{
icon: <KeyIcon className="size-4" />,
label: "API keys",
value: "api-keys",
},
],
label: "Account",
value: "account",
},
{
items: [
{
icon: <SunIcon className="size-4" />,
label: "Light mode",
value: "light",
},
{
icon: <MoonIcon className="size-4" />,
label: "Dark mode",
value: "dark",
},
{
icon: <BarChart2Icon className="size-4" />,
label: "Analytics",
value: "analytics",
},
],
label: "Preferences",
value: "prefs",
},
{
items: [
{
icon: <LogOutIcon className="size-4 text-destructive" />,
label: "Sign out",
value: "signout",
},
],
label: "Session",
value: "session",
},
];
export default function Particle() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Account settings
<KbdGroup>
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
</KbdGroup>
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={sections}>
<CommandInput placeholder="Search settings..." />
<CommandPanel>
<CommandEmpty>No settings found.</CommandEmpty>
<CommandList>
{(section: Section, i: number) => (
<>
{i > 0 && <CommandSeparator key={`sep-${section.value}`} />}
<CommandGroup items={section.items} key={section.value}>
<CommandGroupLabel>{section.label}</CommandGroupLabel>
<CommandCollection>
{(action: Action) => (
<CommandItem
className="gap-2"
key={action.value}
onSelect={() => setOpen(false)}
value={action.value}
>
{action.icon}
{action.label}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
</>
)}
</CommandList>
</CommandPanel>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
Repository Search
Searches across issues, pull requests, branches, and files in one palette. Each result type has a distinct colored icon and a number badge where applicable.
"use client";
import {
CircleDotIcon,
FolderIcon,
GitBranchIcon,
GitPullRequestIcon,
} from "lucide-react";
import { Fragment, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
} from "@/components/ui/command";
type Result = {
icon: React.ReactNode;
label: string;
meta?: string;
repo: string;
type: "issue" | "pr" | "branch" | "file";
value: string;
};
type ResultGroup = { items: Result[]; label: string; value: string };
const results: ResultGroup[] = [
{
items: [
{
icon: <CircleDotIcon className="size-4 text-emerald-500" />,
label: "Fix tooltip positioning",
meta: "#421",
repo: "ui-cnippet",
type: "issue",
value: "i421",
},
{
icon: <CircleDotIcon className="size-4 text-emerald-500" />,
label: "Add keyboard shortcut support",
meta: "#418",
repo: "ui-cnippet",
type: "issue",
value: "i418",
},
],
label: "Issues",
value: "issues",
},
{
items: [
{
icon: <GitPullRequestIcon className="size-4 text-violet-500" />,
label: "feat: carousel thumbnails",
meta: "#89",
repo: "ui-cnippet",
type: "pr",
value: "pr89",
},
{
icon: <GitPullRequestIcon className="size-4 text-violet-500" />,
label: "chore: update dependencies",
meta: "#87",
repo: "ui-cnippet",
type: "pr",
value: "pr87",
},
],
label: "Pull Requests",
value: "prs",
},
{
items: [
{
icon: <GitBranchIcon className="size-4 text-muted-foreground" />,
label: "feat/command-palette",
repo: "ui-cnippet",
type: "branch",
value: "b1",
},
{
icon: <FolderIcon className="size-4 text-muted-foreground" />,
label: "registry/default/ui/command.tsx",
repo: "ui-cnippet",
type: "file",
value: "f1",
},
],
label: "Branches & Files",
value: "refs",
},
];
export default function Particle() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Search repository...
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={results}>
<CommandInput placeholder="Search issues, PRs, branches..." />
<CommandPanel>
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(group: ResultGroup, i: number) => (
<Fragment key={group.value}>
{i > 0 && <CommandSeparator />}
<CommandGroup items={group.items}>
<CommandGroupLabel>{group.label}</CommandGroupLabel>
<CommandCollection>
{(r: Result) => (
<CommandItem
className="gap-2"
key={r.value}
onSelect={() => setOpen(false)}
value={r.value}
>
{r.icon}
<span className="flex-1 text-sm">{r.label}</span>
{r.meta && (
<Badge size="sm" variant="secondary">
{r.meta}
</Badge>
)}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
</Fragment>
)}
</CommandList>
</CommandPanel>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
Go To Page
A navigation palette where frequently accessed pages have keyboard shortcut hints. Unbound pages show a trailing arrow instead.
"use client";
import {
ArrowRightIcon,
GlobeIcon,
LayoutDashboardIcon,
MessageSquareIcon,
PackageIcon,
SettingsIcon,
UsersIcon,
} from "lucide-react";
import { Fragment, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
import { Kbd } from "@/components/ui/kbd";
type NavItem = {
icon: React.ReactNode;
label: string;
shortcut?: string;
value: string;
};
type NavGroup = { items: NavItem[]; label: string; value: string };
const nav: NavGroup[] = [
{
items: [
{
icon: <LayoutDashboardIcon className="size-4" />,
label: "Dashboard",
shortcut: "G D",
value: "dashboard",
},
{
icon: <UsersIcon className="size-4" />,
label: "Users",
shortcut: "G U",
value: "users",
},
{
icon: <PackageIcon className="size-4" />,
label: "Products",
shortcut: "G P",
value: "products",
},
{
icon: <MessageSquareIcon className="size-4" />,
label: "Messages",
value: "messages",
},
],
label: "Navigation",
value: "nav",
},
{
items: [
{
icon: <SettingsIcon className="size-4" />,
label: "Settings",
shortcut: "G S",
value: "settings",
},
{
icon: <GlobeIcon className="size-4" />,
label: "Domains",
value: "domains",
},
],
label: "Admin",
value: "admin",
},
];
export default function Particle() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Go to page...
<Kbd>G</Kbd>
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={nav}>
<CommandInput placeholder="Go to..." />
<CommandPanel>
<CommandEmpty>Page not found.</CommandEmpty>
<CommandList>
{(group: NavGroup, i: number) => (
<Fragment key={group.value}>
{i > 0 && <CommandSeparator />}
<CommandGroup items={group.items}>
<CommandGroupLabel>{group.label}</CommandGroupLabel>
<CommandCollection>
{(item: NavItem) => (
<CommandItem
className="gap-2"
key={item.value}
onSelect={() => setOpen(false)}
value={item.value}
>
{item.icon}
<span className="flex-1">{item.label}</span>
{item.shortcut ? (
<CommandShortcut>{item.shortcut}</CommandShortcut>
) : (
<ArrowRightIcon className="size-3.5 text-muted-foreground" />
)}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
</Fragment>
)}
</CommandList>
</CommandPanel>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
Appearance Picker
Two groups — Theme and Accent Color — with a checkmark on the currently active option. Selecting an item closes the dialog and updates external state.
"use client";
import { CheckIcon, MoonIcon, SunIcon, SunMediumIcon } from "lucide-react";
import { Fragment, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
} from "@/components/ui/command";
type Option = { icon: React.ReactNode; label: string; value: string };
type OptionGroup = { items: Option[]; label: string; value: string };
const optionGroups: OptionGroup[] = [
{
items: [
{ icon: <SunIcon className="size-4" />, label: "Light", value: "light" },
{ icon: <MoonIcon className="size-4" />, label: "Dark", value: "dark" },
{
icon: <SunMediumIcon className="size-4" />,
label: "System",
value: "system",
},
],
label: "Theme",
value: "theme",
},
{
items: [
{
icon: <span className="size-4 rounded-full bg-violet-500" />,
label: "Violet",
value: "violet",
},
{
icon: <span className="size-4 rounded-full bg-blue-500" />,
label: "Blue",
value: "blue",
},
{
icon: <span className="size-4 rounded-full bg-emerald-500" />,
label: "Green",
value: "green",
},
{
icon: <span className="size-4 rounded-full bg-rose-500" />,
label: "Rose",
value: "rose",
},
{
icon: <span className="size-4 rounded-full bg-orange-500" />,
label: "Orange",
value: "orange",
},
],
label: "Accent Color",
value: "accent",
},
];
export default function Particle() {
const [open, setOpen] = useState(false);
const [theme, setTheme] = useState("light");
const [accent, setAccent] = useState("violet");
const handleSelect = (group: string, value: string) => {
if (group === "theme") setTheme(value);
else setAccent(value);
setOpen(false);
};
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Appearance settings
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={optionGroups}>
<CommandInput placeholder="Search appearance..." />
<CommandPanel>
<CommandEmpty>No options found.</CommandEmpty>
<CommandList>
{(group: OptionGroup, i: number) => (
<Fragment key={group.value}>
{i > 0 && <CommandSeparator />}
<CommandGroup items={group.items}>
<CommandGroupLabel>{group.label}</CommandGroupLabel>
<CommandCollection>
{(opt: Option) => {
const isActive =
group.value === "theme"
? theme === opt.value
: accent === opt.value;
return (
<CommandItem
className="gap-2"
key={opt.value}
onSelect={() =>
handleSelect(group.value, opt.value)
}
value={`${group.value}-${opt.value}`}
>
{opt.icon}
<span className="flex-1">{opt.label}</span>
{isActive && (
<CheckIcon className="size-4 text-primary" />
)}
</CommandItem>
);
}}
</CommandCollection>
</CommandGroup>
</Fragment>
)}
</CommandList>
</CommandPanel>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}
Quick Open With Footer
A minimal page-jump palette with notification count badges on relevant items and a CommandFooter showing keyboard navigation hints.
"use client";
import {
ArrowDownIcon,
ArrowUpIcon,
CornerDownLeftIcon,
HashIcon,
LayoutDashboardIcon,
SearchIcon,
UserCircleIcon,
} from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogPopup,
CommandDialogTrigger,
CommandEmpty,
CommandFooter,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
} from "@/components/ui/command";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
type Page = {
badge?: string;
icon: React.ReactNode;
label: string;
value: string;
};
const pages: Page[] = [
{
badge: "12",
icon: <LayoutDashboardIcon className="size-4" />,
label: "Dashboard",
value: "dashboard",
},
{
icon: <UserCircleIcon className="size-4" />,
label: "Profile",
value: "profile",
},
{
badge: "3",
icon: <HashIcon className="size-4" />,
label: "Channels",
value: "channels",
},
{ icon: <SearchIcon className="size-4" />, label: "Search", value: "search" },
];
export default function Particle() {
const [open, setOpen] = useState(false);
return (
<CommandDialog onOpenChange={setOpen} open={open}>
<CommandDialogTrigger render={<Button variant="outline" />}>
Quick open
<KbdGroup>
<Kbd>⌘</Kbd>
<Kbd>P</Kbd>
</KbdGroup>
</CommandDialogTrigger>
<CommandDialogPopup>
<Command items={[{ items: pages, label: "Pages", value: "pages" }]}>
<CommandInput placeholder="Search pages and actions..." />
<CommandPanel>
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(group: { items: Page[]; label: string; value: string }) => (
<CommandGroup items={group.items} key={group.value}>
<CommandGroupLabel>{group.label}</CommandGroupLabel>
<CommandCollection>
{(page: Page) => (
<CommandItem
className="gap-2"
key={page.value}
onSelect={() => setOpen(false)}
value={page.value}
>
{page.icon}
<span className="flex-1">{page.label}</span>
{page.badge && (
<Badge size="sm" variant="secondary">
{page.badge}
</Badge>
)}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
)}
</CommandList>
</CommandPanel>
<CommandFooter>
<div className="flex items-center gap-3">
<KbdGroup>
<Kbd>
<ArrowUpIcon />
</Kbd>
<Kbd>
<ArrowDownIcon />
</Kbd>
</KbdGroup>
<span>navigate</span>
<Kbd>
<CornerDownLeftIcon />
</Kbd>
<span>open</span>
</div>
<div className="flex items-center gap-2">
<Kbd>Esc</Kbd>
<span>close</span>
</div>
</CommandFooter>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}

