Overview
Get Started
Command
A command palette component built with Dialog and Autocomplete for searching and executing commands.
Linear⌘L
Figma⌘F
Slack⌘S
YouTube⌘Y
Raycast⌘R
Clipboard History⌘⇧C
Import Extension⌘I
Create Snippet⌘N
System Preferences⌘,
Window Management⌘⇧W
"use client";
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandShortcut,
} from "@/components/ui/command";
export interface Item {
value: string;
label: string;
shortcut?: string;
}
export const items: 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" },
{ 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 function CommandDemo() {
function handleItemClick(_item: Item) {
alert(`You clicked on ${_item.label} (${_item.value})`);
}
return (
<div className="overflow-hidden rounded-xl border bg-popover">
<Command items={items}>
<div className="mx-2 mt-2 rounded-lg border">
<CommandInput placeholder="Search for apps and commands..." />
</div>
<CommandPanel>
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(item: Item) => (
<CommandItem
key={item.value}
onClick={() => handleItemClick(item)}
value={item.value}
>
<span>{item.label}</span>
{item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
</CommandItem>
)}
</CommandList>
</CommandPanel>
</Command>
</div>
);
}Installation
"use client";
import { Dialog as CommandDialogPrimitive } from "@base-ui/react/dialog";
import { IconSearch } from "@tabler/icons-react";
import { cn } from "@/lib/cn";
import {
Autocomplete,
AutocompleteCollection,
AutocompleteEmpty,
AutocompleteGroup,
AutocompleteGroupLabel,
AutocompleteInput,
AutocompleteInputGroup,
AutocompleteItem,
AutocompleteList,
AutocompleteSeparator,
} from "@/components/ui/autocomplete";
export const CommandDialog = CommandDialogPrimitive.Root;
export const CommandDialogPortal = CommandDialogPrimitive.Portal;
export const commandCreateHandle = CommandDialogPrimitive.createHandle;
export function CommandDialogTrigger(
props: React.ComponentProps<typeof CommandDialogPrimitive.Trigger>
) {
return <CommandDialogPrimitive.Trigger data-slot="command-dialog-trigger" {...props} />;
}
type CommandDialogBackdropVariant = "blur" | "opaque" | "transparent";
export function CommandDialogBackdrop({
className,
variant = "blur",
...props
}: React.ComponentProps<typeof CommandDialogPrimitive.Backdrop> & {
variant?: CommandDialogBackdropVariant;
}) {
return (
<CommandDialogPrimitive.Backdrop
data-slot="command-dialog-backdrop"
className={cn(
"fixed inset-0 z-50 transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
variant === "blur" && "bg-black/30 backdrop-blur-sm",
variant === "opaque" && "bg-black/50",
variant === "transparent" && "bg-transparent",
className
)}
{...props}
/>
);
}
type CommandDialogPopupPosition = "center" | "top-center";
export function CommandDialogViewport({
className,
position = "center",
...props
}: React.ComponentProps<typeof CommandDialogPrimitive.Viewport> & {
position?: CommandDialogPopupPosition;
}) {
return (
<CommandDialogPrimitive.Viewport
data-slot="command-dialog-viewport"
className={cn(
"fixed inset-0 z-50 flex flex-col p-4",
position === "center" && "items-center justify-center",
position === "top-center" &&
"grid grid-rows-[1fr_auto_4fr] justify-items-center *:data-[slot=command-dialog-popup]:row-start-2",
className
)}
{...props}
/>
);
}
export function CommandDialogPopup({
className,
backdropVariant,
position,
portalProps,
...props
}: React.ComponentProps<typeof CommandDialogPrimitive.Popup> & {
backdropVariant?: CommandDialogBackdropVariant;
position?: CommandDialogPopupPosition;
portalProps?: React.ComponentProps<typeof CommandDialogPrimitive.Portal>;
}): React.ReactElement {
return (
<CommandDialogPortal {...portalProps}>
<CommandDialogBackdrop variant={backdropVariant} />
<CommandDialogViewport position={position}>
<CommandDialogPrimitive.Popup
data-slot="command-dialog-popup"
className={cn(
"relative flex min-h-0 w-full min-w-0 max-w-xl -translate-y-[calc(1.25rem*var(--nested-dialogs))] scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border bg-muted text-popover-foreground opacity-[calc(1-0.1*var(--nested-dialogs))] shadow-lg/5 transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0",
className
)}
{...props}
/>
</CommandDialogViewport>
</CommandDialogPortal>
);
}
export function Command({
autoHighlight = "always",
keepHighlight = true,
...props
}: React.ComponentProps<typeof Autocomplete>) {
return (
<Autocomplete
autoHighlight={autoHighlight}
keepHighlight={keepHighlight}
inline
open
{...props}
/>
);
}
export function CommandInput({
className,
startAddon,
...props
}: React.ComponentProps<typeof AutocompleteInput>) {
return (
<AutocompleteInputGroup>
<AutocompleteInput
autoFocus
startAddon={startAddon || <IconSearch />}
className={cn("border-0 bg-transparent! shadow-none focus-visible:ring-0", className)}
{...props}
/>
</AutocompleteInputGroup>
);
}
export function CommandList({
className,
...props
}: React.ComponentProps<typeof AutocompleteList>) {
return (
<AutocompleteList
data-slot="command-list"
className={cn("not-empty:scroll-py-2 not-empty:p-2", className)}
{...props}
/>
);
}
export function CommandEmpty({
className,
...props
}: React.ComponentProps<typeof AutocompleteEmpty>) {
return (
<AutocompleteEmpty
data-slot="command-empty"
className={cn("not-empty:py-6", className)}
{...props}
/>
);
}
export function CommandPanel({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
className={cn(
"flex min-h-0 flex-col bg-popover **:data-[slot=scroll-area-scrollbar]:my-2",
"max-h-80 **:data-[slot=scroll-area-viewport]:data-has-overflow-y:pe-1 **:data-[slot=scroll-area]:flex **:data-[slot=scroll-area]:flex-col",
className
)}
{...props}
/>
);
}
export function CommandGroup(props: React.ComponentProps<typeof AutocompleteGroup>) {
return <AutocompleteGroup data-slot="command-group" {...props} />;
}
export function CommandGroupLabel(props: React.ComponentProps<typeof AutocompleteGroupLabel>) {
return <AutocompleteGroupLabel data-slot="command-group-label" {...props} />;
}
export const CommandCollection = AutocompleteCollection;
export function CommandItem({
className,
...props
}: React.ComponentProps<typeof AutocompleteItem>) {
return (
<AutocompleteItem data-slot="command-item" className={cn("py-1.5", className)} {...props} />
);
}
export function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof AutocompleteSeparator>) {
return (
<AutocompleteSeparator
data-slot="command-separator"
className={cn("my-2", className)}
{...props}
/>
);
}
export function CommandShortcut({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<kbd
data-slot="command-shortcut"
className={cn(
"ms-auto font-medium font-sans text-muted-foreground/80 text-xs tracking-widest",
className
)}
{...props}
/>
);
}
export function CommandFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="command-footer"
className={cn(
"flex items-center justify-between gap-2 border-t px-5 py-3 text-muted-foreground text-xs",
className
)}
{...props}
/>
);
}
export { CommandDialogPrimitive };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
Command with group
Suggestions
Linear⌘L
Figma⌘F
Slack⌘S
Commands
Clipboard History⌘⇧C
Import Extension⌘I
Create Snippet⌘N
"use client";
import { Fragment } from "react";
import {
Command,
CommandCollection,
CommandEmpty,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
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" },
];
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" },
];
export const groupedItems: Group[] = [
{ items: suggestions, value: "Suggestions" },
{ items: commands, value: "Commands" },
];
export function CommandDemo() {
function handleItemClick(_item: Item) {
alert(`You clicked on ${_item.label} (${_item.value})`);
}
return (
<div className="overflow-hidden rounded-xl border bg-popover">
<Command items={groupedItems}>
<div className="mx-2 mt-2 rounded-lg border">
<CommandInput placeholder="Search for apps and commands..." />
</div>
<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>{item.label}</span>
{item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
<CommandSeparator />
</Fragment>
)}
</CommandList>
</CommandPanel>
</Command>
</div>
);
}Command with dialog
"use client";
import { Fragment, useEffect, useState } from "react";
import { IconArrowDown, IconArrowUp, IconCornerDownLeft } from "@tabler/icons-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 function CommandDemo() {
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}>
<div className="px-2.5 py-1.5">
<CommandInput placeholder="Search for apps and commands..." size="lg" />
</div>
<CommandPanel className="rounded-t-xl border-t">
<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>{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>
<IconArrowUp />
</Kbd>
<Kbd>
<IconArrowDown />
</Kbd>
</KbdGroup>
<span>Navigate</span>
</div>
<div className="flex items-center gap-2">
<Kbd>
<IconCornerDownLeft />
</Kbd>
<span>Open</span>
</div>
</div>
<div className="flex items-center gap-2">
<Kbd>Esc</Kbd>
<span>Close</span>
</div>
</CommandFooter>
</Command>
</CommandDialogPopup>
</CommandDialog>
);
}Command with popover
"use client";
import { Fragment, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandEmpty,
CommandGroup,
CommandGroupLabel,
CommandInput,
CommandItem,
CommandList,
CommandPanel,
CommandSeparator,
CommandShortcut,
} from "@/components/ui/command";
import { Popover, PopoverPopup, PopoverTrigger } from "@/components/ui/popover";
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 function CommandDemo() {
const [open, setOpen] = useState(false);
function handleItemClick(_item: Item) {
alert(`You clicked on ${_item.label} (${_item.value})`);
setOpen(false);
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger render={<Button variant="outline" />}>Filter</PopoverTrigger>
<PopoverPopup className="overflow-hidden rounded-lg p-0">
<Command items={groupedItems}>
<div className="px-2 py-1">
<CommandInput placeholder="Search for apps and commands..." />
</div>
<CommandPanel className="border-t">
<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>{item.label}</span>
{item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
<CommandSeparator />
</Fragment>
)}
</CommandList>
</CommandPanel>
</Command>
</PopoverPopup>
</Popover>
);
}