Command
A command menu with search, groups and keyboard navigation. It's the "just type it" escape hatch for your app.
Preview
Usage
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from '@vitnode/core/components/ui/command'<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search Emoji</CommandItem>
</CommandGroup>
</CommandList>
</Command>Type and the list filters itself, best matches first. Empty groups hide. Built on cmdk, so every cmdk prop works.
Groups, icons and shortcuts
CommandGroup takes a heading, and CommandSeparator splits groups. Icons go
first and are sized for you. CommandShortcut is only a label: bind the keys
yourself.
<CommandGroup heading="Settings">
<CommandItem>
<UserIcon />
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />CommandEmpty shows only when nothing matches. Make it kind.
Selecting items
onSelect fires on click and Enter. disabled keeps an item
visible but skipped. Items match by their text: add value when the text isn't
unique, and keywords for other names people might type.
<CommandItem onSelect={() => router.navigate({ to: '/settings' })}>
Settings
</CommandItem>
<CommandItem keywords={['theme', 'night']} value="toggle-dark-mode">
Toggle dark mode
</CommandItem>Dialog
CommandDialog wraps a Command in a Dialog with a
screen-reader-only title and description. Control it with open and
onOpenChange, and bind a shortcut yourself.
const [open, setOpen] = React.useState(false)
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key.toLowerCase() !== 'j') return
if (!event.metaKey && !event.ctrlKey) return
if (event.altKey || event.shiftKey || event.defaultPrevented) return
event.preventDefault()
setOpen(prev => !prev)
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [])
<CommandDialog onOpenChange={setOpen} open={open}>
<Command>
<CommandInput placeholder="What do you need?" />
<CommandList>...</CommandList>
</Command>
</CommandDialog>Why ⌘J and not ⌘K?
The docs search and the AdminCP already own ⌘K / Ctrl+K, so the demo uses ⌘J / Ctrl+J. Pick a free key and remove the listener on unmount. Inside the open palette Ctrl+J moves down a row, so Escape is the way out.
Show the shortcut next to the trigger with Kbd, like the demo.
Your own filtering
Searching an API? Set shouldFilter={false} and render what your query returns.
The AdminCP search works this way.
<Command label="Search members" shouldFilter={false}>
<CommandInput onValueChange={setSearch} value={search} />
<CommandList>
{members.map((member) => (
<CommandItem key={member.id} value={member.id}>
{member.name}
</CommandItem>
))}
</CommandList>
</Command>Accessibility
- The input is a
comboboxand the list alistbox, so screen readers announce the highlighted option. - ↑ / ↓ move, Home / End jump to the
ends, Enter selects.
loopwraps at the edges. - Ctrl+N / Ctrl+J and Ctrl+P /Ctrl+K
also move. Turn them off with
vimBindings={false}. - Give a standalone
Commandalabelso the input has a name.CommandDialogtraps focus and closes on Escape.
Props
Command
Prop
Type
CommandDialog
Prop
Type
CommandItem
Prop
Type