import {
ArchiveIcon,
CalendarDaysIcon,
FolderIcon,
PlusIcon,
SettingsIcon,
UsersIcon,
type LucideIcon,
} from "lucide-react";
import * as React from "react";
import { Badge } from "@/components/ui/badge";
import {
Command,
CommandCollection,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import { Status } from "@/components/ui/status";
import { toast } from "@/components/ui/toast";
type Action = {
value: string;
label: string;
icon: LucideIcon;
active?: boolean;
disabled?: boolean;
};
type Group = { value: string; items: Action[] };
const groups: Group[] = [
{
value: "Projects",
items: [
{ value: "website-redesign", label: "Website redesign", icon: FolderIcon, active: true },
{ value: "mobile-app", label: "Mobile app", icon: FolderIcon },
{ value: "brand-guidelines", label: "Brand guidelines", icon: FolderIcon },
],
},
{
value: "Actions",
items: [
{ value: "new-task", label: "New task", icon: PlusIcon },
{ value: "invite-people", label: "Invite people", icon: UsersIcon },
{ value: "open-calendar", label: "Open calendar", icon: CalendarDaysIcon },
{ value: "settings", label: "Settings", icon: SettingsIcon },
{ value: "archive-project", label: "Archive project", icon: ArchiveIcon, disabled: true },
],
},
];
export default function CommandBasic() {
return (
<Command items={groups} className="h-80 w-full max-w-md">
<CommandInput placeholder="Search projects and actions…" />
<CommandList>
{(group: Group, index: number) => (
<React.Fragment key={group.value}>
{index > 0 && <CommandSeparator />}
<CommandGroup items={group.items} heading={group.value}>
<CommandCollection>
{(item: Action) => (
<CommandItem
key={item.value}
value={item}
disabled={item.disabled}
onSelect={() => toast.add({ title: item.label })}
>
<item.icon />
{item.label}
{item.active && (
<Status variant="success" className="ml-auto text-xs">
Active
</Status>
)}
{item.disabled && (
<Badge variant="muted" className="ml-auto">
Admins only
</Badge>
)}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
</React.Fragment>
)}
</CommandList>
<CommandEmpty>No results found.</CommandEmpty>
</Command>
);
}- Add the registryOnce per project
pnpm dlx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json - Add command
pnpm dlx shadcn@latest add @jarvis/command
Usage
import {
Command,
CommandEmpty,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
type Page = { value: string; label: string; href: string };
const pages: Page[] = [
{ value: "inbox", label: "Inbox", href: "/inbox" },
{ value: "projects", label: "Projects", href: "/projects" },
{ value: "settings", label: "Settings", href: "/settings" },
];
export function PageSearch() {
return (
<Command items={pages} className="h-64 max-w-sm">
<CommandInput placeholder="Go to…" />
<CommandList>
{(page: Page) => (
<CommandItem
key={page.value}
value={page}
onSelect={() => window.location.assign(page.href)}
>
{page.label}
</CommandItem>
)}
</CommandList>
<CommandEmpty>No results found.</CommandEmpty>
</Command>
);
}Pass the items to Command and render each one from the function inside CommandList. Typing
filters them by label; items without one need an itemToStringValue. CommandEmpty shows when
nothing matches. An item's onSelect runs on click or Enter.
The list never scrolls sideways. Text too long for its row ends in an ellipsis, whether it's an
item's own text, a plain span inside it or a group's heading. Components inside an item, such as
a Badge, keep their size, and the row cuts off whatever doesn't fit.
To group items, pass an array of groups, each with its own items, and render each as a
CommandGroup holding a CommandCollection. Put a CommandSeparator between groups.
Composition
Examples
In a dialog
CommandDialog puts the menu in a Dialog for a command palette.
CommandDialogTrigger opens it, and shortcut on CommandDialog takes a chord such as "Mod+K"
that opens and closes it from anywhere on the page. CommandDialogContent holds the Command, with
a title and description for screen readers. Choosing an item with an onSelect closes the
dialog.
shortcut on an item takes a chord too, shows it, and runs onSelect when it's pressed inside the
menu.
import {
CalendarDaysIcon,
FolderPlusIcon,
InboxIcon,
LayoutDashboardIcon,
PlusIcon,
SearchIcon,
SettingsIcon,
UploadIcon,
UsersIcon,
type LucideIcon,
} from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandCollection,
CommandDialog,
CommandDialogContent,
CommandDialogTrigger,
CommandEmpty,
CommandFooter,
CommandGroup,
CommandHint,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
import { toast } from "@/components/ui/toast";
import { useShortcutDisplay, type ShortcutChord } from "@/hooks/use-shortcut";
type Action = { value: string; label: string; icon: LucideIcon; shortcut?: ShortcutChord };
type Group = { value: string; items: Action[] };
const groups: Group[] = [
{
value: "Actions",
items: [
{ value: "new-task", label: "New task", icon: PlusIcon, shortcut: "Mod+J" },
{ value: "new-project", label: "New project", icon: FolderPlusIcon },
{ value: "invite-people", label: "Invite people", icon: UsersIcon },
{ value: "upload-file", label: "Upload file", icon: UploadIcon },
],
},
{
value: "Go to",
items: [
{ value: "dashboard", label: "Dashboard", icon: LayoutDashboardIcon },
{ value: "inbox", label: "Inbox", icon: InboxIcon },
{ value: "calendar", label: "Calendar", icon: CalendarDaysIcon },
{ value: "settings", label: "Settings", icon: SettingsIcon, shortcut: "Mod+Shift+S" },
],
},
];
export default function CommandInDialog() {
const shortcut = useShortcutDisplay("Mod+K");
return (
<CommandDialog shortcut="Mod+K">
<CommandDialogTrigger
render={<Button variant="secondary" className="w-60 justify-between" />}
>
<span className="flex items-center gap-1.5 text-muted-foreground">
<SearchIcon />
Search…
</span>
{shortcut && (
<KbdGroup>
{shortcut.keys.map((key) => (
<Kbd key={key}>{key}</Kbd>
))}
</KbdGroup>
)}
</CommandDialogTrigger>
<CommandDialogContent>
<Command items={groups}>
<CommandInput placeholder="Type a command…" />
<CommandList>
{(group: Group, index: number) => (
<React.Fragment key={group.value}>
{index > 0 && <CommandSeparator />}
<CommandGroup items={group.items} heading={group.value}>
<CommandCollection>
{(action: Action) => (
<CommandItem
key={action.value}
value={action}
shortcut={action.shortcut}
onSelect={() => toast.add({ title: action.label })}
>
<action.icon />
{action.label}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
</React.Fragment>
)}
</CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandFooter>
<CommandHint shortcut={["ArrowUp", "ArrowDown"]}>Navigate</CommandHint>
<CommandHint shortcut="Enter">Open</CommandHint>
<CommandHint shortcut="Escape" className="ml-auto">
Close
</CommandHint>
</CommandFooter>
</Command>
</CommandDialogContent>
</CommandDialog>
);
}The trigger shows its chord with useShortcutDisplay, which writes it the way the reader's platform
does, such as ⌘ K on a Mac and Ctrl K elsewhere. The Kbd
page covers it, along with ShortcutChord, the type of each shortcut, which comes from the
same file.
With a footer
CommandFooter goes last in Command, under the list, and stays at the bottom while the list
scrolls or CommandEmpty shows. It holds hints about the keys the menu takes, or anything else that
belongs under the results. CommandHint is one hint: shortcut takes a chord, or an array of them
such as ["ArrowUp", "ArrowDown"], and shows each as keys before the label, written the way the
reader's platform writes them. It only shows the keys, since the menu already handles the arrows,
Enter and each item's own shortcut. Give a hint className="ml-auto" to push it, and any after
it, to the far end, as the dialog above does with Esc.
Like useShortcutDisplay, a hint can't show its keys until the page hydrates, so it stays hidden
until then. Hints are for keyboard users: screen readers skip them, since the menu's roles already
tell how to move through it. The examples show the footer at every width, but a menu that's mostly
opened on touch screens can hide it on narrow ones with className="max-sm:hidden".
import {
CalendarDaysIcon,
FolderIcon,
InboxIcon,
LayoutDashboardIcon,
PlusIcon,
UsersIcon,
type LucideIcon,
} from "lucide-react";
import * as React from "react";
import {
Command,
CommandCollection,
CommandEmpty,
CommandFooter,
CommandGroup,
CommandHint,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import { toast } from "@/components/ui/toast";
type Action = { value: string; label: string; icon: LucideIcon };
type Group = { value: string; items: Action[] };
const groups: Group[] = [
{
value: "Go to",
items: [
{ value: "dashboard", label: "Dashboard", icon: LayoutDashboardIcon },
{ value: "inbox", label: "Inbox", icon: InboxIcon },
{ value: "projects", label: "Projects", icon: FolderIcon },
{ value: "calendar", label: "Calendar", icon: CalendarDaysIcon },
],
},
{
value: "Actions",
items: [
{ value: "new-task", label: "New task", icon: PlusIcon },
{ value: "invite-people", label: "Invite people", icon: UsersIcon },
],
},
];
export default function CommandWithFooter() {
return (
<Command items={groups} className="h-72 w-full max-w-md">
<CommandInput placeholder="Search pages and actions…" />
<CommandList>
{(group: Group, index: number) => (
<React.Fragment key={group.value}>
{index > 0 && <CommandSeparator />}
<CommandGroup items={group.items} heading={group.value}>
<CommandCollection>
{(item: Action) => (
<CommandItem
key={item.value}
value={item}
onSelect={() => toast.add({ title: item.label })}
>
<item.icon />
{item.label}
</CommandItem>
)}
</CommandCollection>
</CommandGroup>
</React.Fragment>
)}
</CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandFooter>
<CommandHint shortcut={["ArrowUp", "ArrowDown"]}>Navigate</CommandHint>
<CommandHint shortcut="Enter">Select</CommandHint>
</CommandFooter>
</Command>
);
}Props
The tables leave out className, style and render. Parts built on Base UI take them as its styling and composition guides describe.
Command
| Prop | Type | Default |
|---|---|---|
children | React.ReactNode | — |
nameIdentifies the field when a form is submitted. | string | — |
idThe id of the component. | string | — |
requiredWhether the user must choose a value before submitting a form. | boolean | false |
readOnlyWhether the user should be unable to choose a different option from the popup. | boolean | false |
disabledWhether the component should ignore user interaction. | boolean | false |
defaultOpenWhether the popup is initially open. To render a controlled popup, use the | boolean | false |
onOpenChangeCompleteEvent handler called after any animations complete when the popup is opened or closed. | ((open: boolean) => void) | — |
loopFocusWhether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. The first item can then be reached by pressing ArrowDown again from the input, or the last item can be reached by pressing ArrowUp from the input. The input is always included in the focus loop per ARIA Authoring Practices. When disabled, focus does not move when on the last element and the user presses ArrowDown, or when on the first element and the user presses ArrowUp. | boolean | true |
inputRefA ref to the hidden input element. | React.Ref<HTMLInputElement> | — |
gridWhether list items are presented in a grid layout. When enabled, arrow keys navigate across rows and columns inferred from DOM rows. | boolean | false |
virtualizedWhether the items are being externally virtualized. | boolean | false |
modalDetermines if the popup enters a modal state when open.
On touch devices, a | boolean | false |
limitThe maximum number of items to display in the list. | number | -1 |
localeThe locale to use for string comparison. Defaults to the user's runtime locale. | Intl.LocalesArgument | — |
formIdentifies the form that owns the internal input. Useful when the autocomplete is rendered outside the form. | string | — |
filteredItemsFiltered items to display in the list.
When provided, the list uses these items instead of filtering the | readonly unknown[] | readonly Group<unknown>[] | — |
filterFilter function used to match items against the input query. | ((item: unknown, query: string, itemToString?: ((item: unknown) => string) | undefined) => boolean) | null | — |
modeControls how the autocomplete behaves with respect to list filtering and inline autocompletion.
| "both" | "inline" | "list" | "none" | 'list' |
autoHighlightWhether the first matching item is highlighted automatically.
| "always" | boolean | "always" |
keepHighlightWhether the highlighted item should be preserved when the pointer leaves the list. | boolean | true |
highlightItemOnHoverWhether moving the pointer over items should highlight them.
Disabling this prop allows CSS | boolean | true |
defaultValueThe uncontrolled input value of the autocomplete when it's initially rendered. To render a controlled autocomplete, use the | string | number | readonly string[] | — |
valueThe input value of the autocomplete. Use when controlled. | string | number | readonly string[] | — |
onValueChangeEvent handler called when the input value of the autocomplete changes. | ((value: string, eventDetails: AutocompleteRootChangeEventDetails) => void) | — |
submitOnItemClickWhether clicking an item should submit the autocomplete's owning form. By default, clicking an item via a pointer or Enter key does not submit the owning form. Useful when the autocomplete is used as a single-field form search input. | boolean | false |
itemToStringValueWhen the item values are objects ( | ((itemValue: unknown) => string) | — |
actionsRefA ref to imperative actions.
| React.RefObject<AutocompleteRootActions | null> | — |
onOpenChangeEvent handler called when the popup is opened or closed. | ((open: boolean, eventDetails: AutocompleteRootOpenChangeEventDetails) => void) | — |
onItemHighlightedCallback fired when an item is highlighted or unhighlighted.
Receives the highlighted item value (or
| ((highlightedValue: unknown, eventDetails: AutocompleteRootHighlightEventDetails) => void) | — |
openOnInputClickWhether the popup opens when clicking the input. | boolean | false |
items | readonly unknown[] | — |
CommandDialog
| Prop | Type | Default |
|---|---|---|
openWhether the dialog is currently open. | boolean | — |
defaultOpenWhether the dialog is initially open. To render a controlled dialog, use the | boolean | false |
modalDetermines if the dialog enters a modal state when open.
When | "trap-focus" | boolean | true |
onOpenChangeEvent handler called when the dialog is opened or closed. | ((open: boolean, eventDetails: DialogRoot.ChangeEventDetails) => void) | — |
onOpenChangeCompleteEvent handler called after any animations complete when the dialog is opened or closed. | ((open: boolean) => void) | — |
disablePointerDismissalWhether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it. | boolean | false |
actionsRefA ref to imperative actions.
| React.RefObject<DialogRootActions | null> | — |
handleA handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method. | DialogHandle<unknown> | — |
childrenThe content of the dialog.
This can be a regular React node or a render function that receives the | PayloadChildRenderFunction<unknown> | React.ReactNode | — |
triggerIdID of the trigger that the dialog is associated with.
This is useful in conjunction with the | string | null | — |
defaultTriggerIdID of the trigger that the dialog is associated with.
This is useful in conjunction with the | string | null | — |
shortcutA TanStack Hotkeys chord, such as "Mod+K", that opens and closes the dialog from anywhere on the page. | ShortcutChord | — |
CommandDialogTrigger
| Prop | Type | Default |
|---|---|---|
handleA handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. | DialogHandle<unknown> | — |
payloadA payload to pass to the dialog when it is opened. | unknown | — |
idID of the trigger. In addition to being forwarded to the rendered element,
it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot | string | — |
nativeButtonWhether the component renders a native | boolean | true |
CommandDialogContent
| Prop | Type | Default |
|---|---|---|
initialFocusDetermines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.
| boolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void) | — |
finalFocusDetermines the element to focus when the dialog is closed.
| boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void) | — |
titleNames the dialog for screen readers. | string | "Command Palette" |
showCloseButton | boolean | false |
descriptionDescribes the dialog for screen readers. | string | "Search for a command to run..." |
CommandInput
| Prop | Type | Default |
|---|---|---|
disabledWhether the component should ignore user interaction. | boolean | false |
CommandList
| Prop | Type | Default |
|---|---|---|
children | ((item: any, index: number) => React.ReactNode) | React.ReactNode | — |
CommandGroup
| Prop | Type | Default |
|---|---|---|
itemsItems to be rendered within this group.
When provided, child | readonly any[] | — |
heading | React.ReactNode | — |
CommandCollection
| Prop | Type | Default |
|---|---|---|
children | (item: any, index: number) => React.ReactNode | — |
CommandSeparator
| Prop | Type | Default |
|---|---|---|
orientationThe orientation of the separator. | "horizontal" | "vertical" | 'horizontal' |
CommandItem
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native | boolean | false |
children | React.ReactNode | — |
onClickAn optional click handler for the item when selected.
It fires when clicking the item with the pointer, as well as when pressing | ((event: BaseUIEvent<React.MouseEvent<HTMLDivElement, MouseEvent>>) => void) | — |
indexThe index of the item in the list. Improves performance when specified by avoiding the need to calculate the index automatically from the DOM. | number | — |
valueA unique value that identifies this item. | any | null |
disabledWhether the component should ignore user interaction. | boolean | false |
onSelectRuns on click or Enter, instead of copying the item into the input. Inside a | (() => void) | — |
shortcutA TanStack Hotkeys chord, such as "Mod+J", that runs | ShortcutChord | — |
CommandHint
| Prop | Type | Default |
|---|---|---|
shortcutA TanStack Hotkeys chord, or several, shown as keys before the hint's label. It only shows them, without binding them. | readonly ShortcutChord[] | ShortcutChord | — |