Command
Fast, composable command palette.
import * as stylex from '@stylexjs/stylex';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
} from '@/components/ui/command';
import { ring } from '@/lib/stylex-utils';
import { container } from '@/lib/constants.stylex';
import { shadow } from '@/lib/tokens.stylex';
const commands = [
{ value: 'calendar', label: 'Calendar', group: 'Suggestions' },
{ value: 'search-emoji', label: 'Search emoji', group: 'Suggestions' },
{ value: 'calculator', label: 'Calculator', group: 'Suggestions' },
{ value: 'profile', label: 'Profile', group: 'Settings', shortcut: '⌘P' },
{ value: 'billing', label: 'Billing', group: 'Settings', shortcut: '⌘B' },
{ value: 'settings', label: 'Settings', group: 'Settings', shortcut: '⌘S' },
];
type CommandEntry = (typeof commands)[number];
export default function CommandDemo() {
return (
<Command
items={commands}
itemToStringLabel={(item: CommandEntry) => item.label}
style={[styles.root, ring({ shadow: shadow.md })]}
>
<CommandInput placeholder="Type a command or search…" />
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(item: CommandEntry) => (
<CommandItem key={item.value} value={item}>
{item.label}
{item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
</CommandItem>
)}
</CommandList>
</Command>
);
}
const styles = stylex.create({
root: {
maxWidth: container.lg,
},
});
Install
npx shadcn@latest add @madeui/command
Usage
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
} from '@/components/ui/command';
Composition
<Command items={items}>
<CommandInput />
<CommandEmpty />
<CommandList>
{(item) => <CommandItem value={item} />}
</CommandList>
</Command>
Groups
import * as React from 'react';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from '@/components/ui/command';
function CalendarIcon() {
return (
<svg
width="16"
height="16"
viewBox={`0 0 16 16`}
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden
>
<rect x="2" y="3" width="12" height="11" rx="1.5" />
<path d={`M2 6.5h12M5 1.5v3M11 1.5v3`} />
</svg>
);
}
function SmileIcon() {
return (
<svg
width="16"
height="16"
viewBox={`0 0 16 16`}
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden
>
<circle cx="8" cy="8" r="6.5" />
<path d={`M5.5 9.5c.6.8 1.4 1.2 2.5 1.2s1.9-.4 2.5-1.2`} />
<path d={`M6 6h.01M10 6h.01`} />
</svg>
);
}
function UserIcon() {
return (
<svg
width="16"
height="16"
viewBox={`0 0 16 16`}
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden
>
<circle cx="8" cy="5.5" r="2.5" />
<path d={`M2.5 14c.8-2.8 3-4.5 5.5-4.5s4.7 1.7 5.5 4.5`} />
</svg>
);
}
function CardIcon() {
return (
<svg
width="16"
height="16"
viewBox={`0 0 16 16`}
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden
>
<rect x="1.5" y="4" width="13" height="9" rx="1.5" />
<path d={`M1.5 7h13`} />
</svg>
);
}
const commandGroups = [
{
value: 'suggestions',
label: 'Suggestions',
items: [
{ value: 'calendar', label: 'Calendar', icon: <CalendarIcon /> },
{ value: 'search-emoji', label: 'Search emoji', icon: <SmileIcon /> },
],
},
{
value: 'settings',
label: 'Settings',
items: [
{
value: 'profile',
label: 'Profile',
icon: <UserIcon />,
shortcut: '⌘P',
},
{ value: 'billing', label: 'Billing', icon: <CardIcon />, shortcut: '⌘B' },
],
},
];
export default function CommandGroups() {
return (
<Command items={commandGroups}>
<CommandInput placeholder="Type a command or search…" />
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(group: (typeof commandGroups)[number]) => (
<React.Fragment key={group.value}>
<CommandGroup heading={group.label}>
{group.items.map((item) => (
<CommandItem key={item.value} value={item}>
{item.icon}
{item.label}
{'shortcut' in item && item.shortcut && (
<CommandShortcut>{item.shortcut}</CommandShortcut>
)}
</CommandItem>
))}
</CommandGroup>
{group.value !==
commandGroups[commandGroups.length - 1].value && (
<CommandSeparator />
)}
</React.Fragment>
)}
</CommandList>
</Command>
);
}
Scrollable
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
const fruits = [
'Apple', 'Apricot', 'Banana', 'Blackberry', 'Blueberry', 'Cantaloupe',
'Cherry', 'Clementine', 'Coconut', 'Cranberry', 'Date', 'Dragonfruit',
'Elderberry', 'Fig', 'Grape', 'Grapefruit', 'Guava', 'Honeydew', 'Kiwi',
'Lemon', 'Lime', 'Lychee', 'Mango', 'Mandarin', 'Nectarine', 'Orange',
'Papaya', 'Passionfruit', 'Peach', 'Pear', 'Persimmon', 'Pineapple',
'Plum', 'Pomegranate', 'Raspberry', 'Star fruit', 'Strawberry',
'Tangerine', 'Watermelon',
].map((label) => ({ label, value: label }));
export default function CommandScrollable() {
return (
<Command items={fruits} itemToStringLabel={(item) => item.label}>
<CommandInput placeholder="Search fruit…" />
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(item: (typeof fruits)[number]) => (
<CommandItem key={item.value} value={item}>
{item.label}
</CommandItem>
)}
</CommandList>
</Command>
);
}
Keyboard shortcut
Open the palette with ⌘K / Ctrl K using the useHotkey helper
(npx shadcn@latest add @madeui/use-hotkey).
'use client';
import * as React from 'react';
import { Button } from '@/components/ui/button';
import {
Command,
CommandDialog,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { Kbd, KbdGroup } from '@/components/ui/kbd';
import { useHotkey } from '@/lib/use-hotkey';
const commands = [
{ value: 'calendar', label: 'Calendar' },
{ value: 'search-emoji', label: 'Search emoji' },
{ value: 'calculator', label: 'Calculator' },
];
type CommandEntry = (typeof commands)[number];
export default function CommandDialogHotkey() {
const [open, setOpen] = React.useState(false);
useHotkey('k', () => setOpen((value) => !value));
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
Open command palette
<KbdGroup>
<Kbd>⌘</Kbd>
<Kbd>K</Kbd>
</KbdGroup>
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command
items={commands}
itemToStringLabel={(item: CommandEntry) => item.label}
>
<CommandInput placeholder="Type a command or search…" />
<CommandEmpty>No results found.</CommandEmpty>
<CommandList>
{(item: CommandEntry) => (
<CommandItem
key={item.value}
value={item}
onClick={() => setOpen(false)}
>
{item.label}
</CommandItem>
)}
</CommandList>
</Command>
</CommandDialog>
</>
);
}
API reference
Built on Base UI Autocomplete in inline open mode — the filtered list renders in place with no popup, like a command palette, with no extra dependency. Pass items to Command; filtering and highlight ship with the primitive, and CommandList accepts a render function over the filtered items. All other props are forwarded to the Base UI Root (filter, onValueChange, itemToStringLabel, …); see the Base UI Autocomplete API reference.
CommandDialog
Wraps the palette in our Dialog (installed as a dependency), positioned at the top third with no close button.
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string |
'Command Palette' |
Screen-reader-only dialog title. |
description |
string |
'Search for a command to run…' |
Screen-reader-only description. |
CommandGroup
| Prop | Type | Default | Description |
|---|---|---|---|
heading |
ReactNode |
— | Renders a muted group label above the items. |
Every part accepts a style prop (StyleXStyles, merged last).