Components · Command
Command menu
A palette for running things: navigate, act, toggle. cmdk owns filtering, ranking, the active-descendant model and scroll-into-view; LUC owns the palette layout, the search field, the grouping rhythm, the active row language and the shortcut column.
Examples
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandMenu,
CommandSeparator,
useCommandPages,
} from "@luc/ui";Open it
The Showcase registers the hotkey globally and the header carries a visible affordance — a shortcut nobody can discover is not a feature.
Trigger
⌘K on macOS, Ctrl K elsewhere. The hook lives in @luc/ui; a product decides when to enable it.
Last action
Nothing yet — open the palette and run something.
Filtering
Type to filter across labels and keywords. Ranking is cmdk's; the markup is ours.
useCommandHotkey(() => setOpen(true));
<CommandMenu open={open} onOpenChange={setOpen} label="Command menu">
<Command>
<CommandInput placeholder="Search commands…" />
<CommandList>
<CommandGroup heading="Navigate">
<CommandItem shortcut="G C" onSelect={() => router.push("/charts")}>Go to charts</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</CommandMenu>Semantics
A command menu runs things. It is not a dropdown.
- Use Select to pick a known value; the choice is the outcome.
- Use Combobox when the list is long or comes from a server.
- Use CommandMenu when the user is invoking behaviour, not choosing a field.
- Never wire Select to navigation: a select that redirects is a trap.
Command + Dialog:
role="dialog" shell with role="listbox" results inside,
role="option" rows, aria-activedescendant on the input,
Escape closes, Enter runs the highlighted command.Nested pages
One palette can hold sub-pages: push a page, clear the search, pop with Backspace.
Open the palette below and choose “Switch theme”. The page stack is a hook, not a router: useCommandPages() returns push, pop, reset and the keydown handler.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
Command | 11 documented props | ({ className, label, loop, ...props }: CommandProps) => import("@types/react").JSX.Element |
CommandEmpty | 2 documented props | ({ className, children, ...props }: React.ComponentPropsWithRef<typeof CmdkCommand.Empty>) => import("@types/react").JSX.Element |
CommandGroup | 5 documented props | ({ className, heading, ...props }: React.ComponentPropsWithRef<typeof CmdkCommand.Group>) => import("@types/react").JSX.Element |
CommandInput | 3 documented props | ({ className, ...props }: CommandInputProps) => import("@types/react").JSX.Element |
CommandItem | 10 documented props | ({ className, children, icon, shortcut, description, ...props }: CommandItemProps) => import("@types/react").JSX.Element |
CommandList | 3 documented props | ({ className, ...props }: CommandListProps) => import("@types/react").JSX.Element |
CommandLoading | 4 documented props | ({ className, children, ...props }: React.ComponentPropsWithRef<typeof CmdkCommand.Loading>) => import("@types/react").JSX.Element |
CommandMenu | 7 documented props | ({ open, onOpenChange, children, label, description, footer, className, }: CommandMenuProps) => import("@types/react").JSX.Element |
CommandSeparator | 2 documented props | ({ className, ...props }: React.ComponentPropsWithRef<typeof CmdkCommand.Separator>) => import("@types/react").JSX.Element |
useCommandHotkey | export | (onOpen: () => void, enabled?: boolean) => void |
useCommandPages | export | () => { pages: string[]; page: string | undefined; search: string; setSearch: import("@types/react").Dispatch<import("@types/react").SetStateAction<string>>; push: (next: string) => void; pop: () => … |
Command props
Type: Children & Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof HTMLAttributes<...>> & { ...; } & { .…
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
children?inherited | ReactNode | — | |
defaultValue?inherited | string | (readonly string[] & string) | undefined | — | Optional default item value when it is initially rendered. |
disablePointerSelection?inherited | boolean | undefined | — | Optionally set to `true` to disable selection via pointer events. |
filter?inherited | CommandFilter | undefined | — | Custom filter function for whether each command menu item should matches the given search query. It should return a number between 0 and 1, with 1 being the best match and 0 being… |
label?inherited | string | undefined | — | Accessible label for this command menu. Not shown visibly. |
loop?inherited | boolean | undefined | — | Optionally set to `true` to turn on looping around when using the arrow keys. |
onValueChange?inherited | ((value: string) => void) | undefined | — | Event handler called when the selected item of the menu changes. |
shouldFilter?inherited | boolean | undefined | — | Optionally set to `false` to turn off the automatic filtering and sorting. If `false`, you must conditionally render valid items based on the search query yourself. |
value?inherited | string | undefined | — | Optional controlled state of the selected command menu item. |
vimBindings?inherited | boolean | undefined | — | Set to `false` to disable ctrl+n/j/p/k shortcuts. Defaults to `true`. |
CommandEmpty props
Type: Children & Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof HTMLAttributes<...>> & { ...; } & { .…
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
children?inherited | ReactNode | — |
CommandGroup props
Type: Children & Omit<Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof HTMLAttributes<...>> & { ...; } …
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
children?inherited | ReactNode | — | |
forceMount?inherited | boolean | undefined | — | Whether this group is forcibly rendered regardless of filtering. |
heading?inherited | ReactNode | — | Optional heading to render for this group. |
value?inherited | string | undefined | — | If no heading is provided, you must provide a value that is unique for this group. |
CommandInput props
Type: Omit<Pick<Pick<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "key" | keyof InputHTMLAttributes<...>> & { ...;…
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
onValueChange?inherited | ((search: string) => void) | undefined | — | Event handler called when the search value changes. |
value?inherited | string | undefined | — | Optional controlled state for the value of the search input. |
CommandItem props
Type: CommandItemProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
children?inherited | ReactNode | — | |
description? | ReactNode | — | Optional secondary line. |
disabled?inherited | boolean | undefined | — | Whether this item is currently disabled. |
forceMount?inherited | boolean | undefined | — | Whether this item is forcibly rendered regardless of filtering. |
icon? | ReactNode | — | Leading icon. Decorative — the label carries the meaning. |
keywords?inherited | string[] | undefined | — | Optional keywords to match against when filtering. |
onSelect?inherited | ((value: string) => void) | undefined | — | Event handler for when this item is selected, either via click or keyboard selection. |
shortcut? | ReactNode | — | Trailing keyboard hint, e.g. `"⌘K"`. |
value?inherited | string | undefined | — | A unique value for this item. If no value is provided, it will be inferred from `children` or the rendered `textContent`. If your `textContent` changes between renders, you _must_… |
CommandList props
Type: Children & Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof HTMLAttributes<...>> & { ...; } & { .…
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
children?inherited | ReactNode | — | |
label?inherited | string | undefined | — | Accessible label for this List of suggestions. Not shown visibly. |
CommandLoading props
Type: Children & Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof HTMLAttributes<...>> & { ...; } & { .…
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
children?inherited | ReactNode | — | |
label?inherited | string | undefined | — | Accessible label for this loading progressbar. Not shown visibly. |
progress?inherited | number | undefined | — | Estimated progress of loading asynchronous options. |
CommandMenu props
Type: CommandMenuProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
children | ReactNode | — | |
className? | string | undefined | — | |
description? | ReactNode | — | Description under the search field — context, not decoration. |
footer? | ReactNode | — | Footer strip: hints, counts, or a live status line. |
label? | string | undefined | — | Accessible name for the listbox. |
onOpenChange | (open: boolean) => void | — | |
open | boolean | — |
CommandSeparator props
Type: Pick<Pick<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof HTMLAttributes<HTMLDivElement>> & { ...; } & { .…
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
alwaysRender?inherited | boolean | undefined | — | Whether this separator should always be rendered. Useful if you disable automatic filtering. |
asChild?inherited | boolean | undefined | — |
Usage
import { Command, CommandInput, CommandList, CommandGroup, CommandItem, CommandEmpty } from "@luc/ui/components/command";<Command>
<CommandInput placeholder="Search actions…" />
<CommandList>
<CommandGroup heading="Actions">
<CommandItem>Export the run</CommandItem>
</CommandGroup>
<CommandEmpty>No matching action.</CommandEmpty>
</CommandList>
</Command>Guidelines
Do
- Use it for a searchable set of actions the reader already knows by name; the query filters the list as they type.
- Group results and keep `CommandEmpty` written for the empty case — a blank list reads as a broken search.
Don't
- Use it as a navigation menu that is always open; a palette is summoned, and `CommandMenu` plus `useCommandHotkey` is the composed form.
Accessibility
The list is a `listbox` with the input as its combobox: Arrow keys move the active option, Enter chooses it, and the active option is announced as it changes. `CommandEmpty` is a live region, so an empty result is spoken.
Related