Command
Command palette example: built on cmdk with a search input, filtered list, groups, items, shortcuts and separators. CommandDialog opens it inside a Dialog, like a quick-search menu.
pnpm dlx shadcn add @01orbital/command"use client"
import * as React from "react"
import { CheckIcon, MagnifyingGlassIcon } from "@phosphor-icons/react"
import { Command as CommandPrimitive } from "cmdk"
import { cn } from "cn"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"
function Command({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"flex size-full flex-col overflow-hidden rounded-4xl bg-popover p-1 text-popover-foreground",
className
)}
{...props}
/>
)
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
className,
showCloseButton = false,
...props
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
title?: string
description?: string
className?: string
showCloseButton?: boolean
children: React.ReactNode
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent
className={cn(
"top-1/3 translate-y-0 overflow-hidden rounded-4xl! p-0",
className
)}
showCloseButton={showCloseButton}
>
{children}
</DialogContent>
</Dialog>
)
}
function CommandInput({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div data-slot="command-input-wrapper" className="p-1 pb-0">
<InputGroup className="h-9 bg-input/50">
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
<InputGroupAddon>
<MagnifyingGlassIcon className="size-4 shrink-0 opacity-50" />
</InputGroupAddon>
</InputGroup>
</div>
)
}
function CommandList({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
className
)}
{...props}
/>
)
}
function CommandEmpty({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className={cn("py-6 text-center text-sm", className)}
{...props}
/>
)
}
function CommandGroup({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"overflow-hidden p-1.5 text-foreground **:[[cmdk-group-heading]]:px-3 **:[[cmdk-group-heading]]:py-2 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
)
}
function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("my-1.5 h-px bg-border/50", className)}
{...props}
/>
)
}
function CommandItem({
className,
children,
...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"group/command-item relative flex cursor-default items-center gap-2 rounded-2xl px-3 py-2 text-sm font-medium outline-hidden select-none in-data-[slot=dialog-content]:rounded-3xl data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:*:[svg]:text-foreground",
className
)}
{...props}
>
{children}
<CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
</CommandPrimitive.Item>
)
}
function CommandShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground",
className
)}
{...props}
/>
)
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}
import {
CalculatorIcon,
CalendarIcon,
CreditCardIcon,
GearIcon,
SmileyIcon,
UserIcon,
} from "@phosphor-icons/react/dist/ssr"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command"
// The copy: change text here, not in the markup.
const COPY = {
placeholder: "Type a command or search...",
empty: "No results found.",
suggestions: "Suggestions",
settings: "Settings",
} as const
const SUGGESTIONS = [
{ icon: CalendarIcon, label: "Calendar", disabled: false },
{ icon: SmileyIcon, label: "Search Emoji", disabled: false },
{ icon: CalculatorIcon, label: "Calculator", disabled: true },
] as const
const SETTINGS = [
{ icon: UserIcon, label: "Profile", shortcut: "⌘P" },
{ icon: CreditCardIcon, label: "Billing", shortcut: "⌘B" },
{ icon: GearIcon, label: "Settings", shortcut: "⌘S" },
] as const
/** Command, after shadcn's example. */
export default function CommandDemo() {
return (
<Command className="max-w-sm rounded-lg border">
<CommandInput placeholder={COPY.placeholder} />
<CommandList>
<CommandEmpty>{COPY.empty}</CommandEmpty>
<CommandGroup heading={COPY.suggestions}>
{SUGGESTIONS.map((item) => (
<CommandItem key={item.label} disabled={item.disabled}>
<item.icon />
<span>{item.label}</span>
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup heading={COPY.settings}>
{SETTINGS.map((item) => (
<CommandItem key={item.label}>
<item.icon />
<span>{item.label}</span>
<CommandShortcut>{item.shortcut}</CommandShortcut>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
)
}
Dependencies
npm: @phosphor-icons/react, cmdk, cn. The CLI installs them for you.
Registry pieces it installs with it: