Command
Command palette keyboard-first construída sobre cmdk. Picker inline para padrões de Combobox ou dialog flutuante Cmd+K para navegação global.
Visão geral
Command é o primitivo certo para pickers keyboard-first e command palettes: selects buscáveis inline (Combobox), launchers de ação, buscadores de arquivos ou docs. Use Command diretamente quando a palette vive no lugar (tipicamente dentro de um Popover para o padrão de Combobox); use CommandDialog para o padrão canônico de palette flutuante Cmd+K usado por GitHub, Linear, Vercel e Notion.
Sempre inclua uma dica de teclado no placeholder do input ("Type a command or search..."), feche com Escape, dê foco ao input na abertura e agrupe os itens por intenção (Navigation, Actions, Recent).
Preview
'use client';import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator,} from '@gremorie/rx-overlays';export function CommandPreview() { return ( <Command className="rounded-lg border max-w-md"> <CommandInput placeholder="Type a command or search..." /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Suggestions"> <CommandItem>Calendar</CommandItem> <CommandItem>Search emoji</CommandItem> <CommandItem>Calculator</CommandItem> </CommandGroup> <CommandSeparator /> <CommandGroup heading="Settings"> <CommandItem>Profile</CommandItem> <CommandItem>Billing</CommandItem> <CommandItem>Settings</CommandItem> </CommandGroup> </CommandList> </Command> );}Anatomia
Command container raiz da lista cmdk
├─ CommandInput campo de busca com lupa à esquerda
└─ CommandList região de resultados com scroll
├─ CommandEmpty mensagem exibida quando nada corresponde
├─ CommandGroup cluster de itens com rótulo
│ └─ CommandItem uma linha selecionável
│ └─ CommandShortcut dica de teclado alinhada à direita
└─ CommandSeparator divisor entre gruposInstalação
bash npx gremorie@latest add rx-command bash pnpm dlx gremorie@latest add rx-command bash yarn dlx gremorie@latest add rx-command bash bunx --bun gremorie@latest add rx-command Uso
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@gremorie/rx-overlays";
export function Example() {
return (
<Command className="rounded-lg border max-w-md">
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search emoji</CommandItem>
<CommandItem>Calculator</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>Profile</CommandItem>
<CommandItem>Billing</CommandItem>
<CommandItem>Settings</CommandItem>
</CommandGroup>
</CommandList>
</Command>
);
}A edição Angular deste componente hoje é distribuída a partir do source (veja o workbench para o lado a lado); a entrada no registry vem a seguir.
API
<Command>
Estende o root Command do cmdk. Props notáveis:
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Valor do item selecionado controlado. |
onValueChange | (value: string) => void | - | Disparado quando a seleção muda via setas ou input. |
shouldFilter | boolean | true | Desative para controlar a filtragem você mesmo (ex.: busca no servidor). |
filter | (value: string, search: string, keywords?: string[]) => number | default fuzzy match | Scoring de filtro customizado (retorne 0 para ocultar). |
loop | boolean | false | Faz a navegação por setas dar a volta do último ao primeiro item. |
Todas as demais props do Command do cmdk são encaminhadas.
<CommandInput>
Input de busca que dirige a filtragem. Envolvido em uma flex row com um ícone de busca à esquerda.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Valor do input controlado. |
onValueChange | (value: string) => void | - | Disparado quando o input muda. |
Estende o Command.Input do cmdk.
<CommandList>
Container com scroll para os resultados. Limitado a max-h-[300px] com scroll-py-1. Estende o Command.List do cmdk.
<CommandEmpty>
Renderizado quando nenhum item corresponde à busca atual. Coloque dentro do CommandList, antes de qualquer grupo.
<CommandGroup>
Grupo de itens com um heading opcional.
| Prop | Type | Default | Description |
|---|---|---|---|
heading | string | ReactNode | - | Título de seção acima dos itens. |
Estende o Command.Group do cmdk.
<CommandItem>
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | text content | Valor interno usado para filtragem e seleção. |
onSelect | (value: string) => void | - | Disparado quando o item é ativado (Enter ou clique). |
disabled | boolean | false | Desabilita a interação; renderiza a 50% de opacidade. |
keywords | string[] | [] | Termos extras que casam com este item na filtragem. |
<CommandSeparator>
Divisor fino bg-border entre grupos. Ocultado automaticamente quando a filtragem remove os grupos vizinhos.
<CommandShortcut>
<span> alinhado à direita para dicas de teclado dentro dos itens (ex.: ⌘N). Helper de layout puro.
<CommandDialog>
Envolve Command em um Dialog para o padrão canônico de palette Cmd+K.
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | "Command Palette" | Título para leitor de tela (visualmente oculto). |
description | string | "Search for a command to run..." | Descrição para leitor de tela (visualmente oculta). |
showCloseButton | boolean | true | Renderiza o botão X de fechar no dialog. |
Todas as demais props do Dialog (open, onOpenChange, etc.) são encaminhadas.
Composição
<Command>detém o estado de busca e a lógica de filtragem.<CommandInput>fica no topo como a busca controlada.<CommandList>guarda os resultados.- Dentro da lista:
CommandEmpty(estado sem resultados) seguido de um ou mais blocosCommandGroup.CommandSeparatordivide os grupos. - Itens: cada
CommandItemexpõeonSelect; adicioneCommandShortcutpara dicas de teclado.
Para o padrão de palette flutuante, envolva tudo em CommandDialog e alterne a prop open no Cmd+K.
Variações
Grupos com shortcuts
Uma palette inline agrupada por intenção, com dicas de shortcut alinhadas à direita e um estado CommandEmpty quando a busca não retorna nada.
'use client';import { ArchiveIcon, FileTextIcon, InboxIcon, SettingsIcon, UserIcon,} from 'lucide-react';import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,} from '@gremorie/rx-overlays';export function CommandGroupsPreview() { return ( <Command className="max-w-md rounded-lg border"> <CommandInput placeholder="Type a command or search..." /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Navigate"> <CommandItem> <InboxIcon /> Inbox <CommandShortcut>G I</CommandShortcut> </CommandItem> <CommandItem> <FileTextIcon /> Documents <CommandShortcut>G D</CommandShortcut> </CommandItem> <CommandItem> <ArchiveIcon /> Archive <CommandShortcut>G A</CommandShortcut> </CommandItem> </CommandGroup> <CommandSeparator /> <CommandGroup heading="Account"> <CommandItem> <UserIcon /> Profile </CommandItem> <CommandItem> <SettingsIcon /> Settings <CommandShortcut>⌘,</CommandShortcut> </CommandItem> </CommandGroup> </CommandList> </Command> );}Dialog Cmd+K
O launcher flutuante canônico. O trigger abaixo abre o CommandDialog; Cmd+K (ou Ctrl+K) também o alterna.
Command Palette
Search for a command to run...
'use client';import { CalculatorIcon, CalendarIcon, CreditCardIcon, SettingsIcon, SmileIcon, UserIcon,} from 'lucide-react';import { useEffect, useState } from 'react';import { Button } from '@gremorie/rx-forms';import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut,} from '@gremorie/rx-overlays';export function CommandDialogPreview() { const [open, setOpen] = useState(false); useEffect(() => { const onKey = (event: KeyboardEvent) => { if (event.key === 'k' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); setOpen((value) => !value); } }; document.addEventListener('keydown', onKey); return () => document.removeEventListener('keydown', onKey); }, []); return ( <> <Button variant="outline" onClick={() => setOpen(true)}> Open command palette <CommandShortcut>⌘K</CommandShortcut> </Button> <CommandDialog open={open} onOpenChange={setOpen}> <CommandInput placeholder="Type a command or search..." /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Suggestions"> <CommandItem onSelect={() => setOpen(false)}> <CalendarIcon /> Calendar </CommandItem> <CommandItem onSelect={() => setOpen(false)}> <SmileIcon /> Search emoji </CommandItem> <CommandItem onSelect={() => setOpen(false)}> <CalculatorIcon /> Calculator </CommandItem> </CommandGroup> <CommandSeparator /> <CommandGroup heading="Settings"> <CommandItem onSelect={() => setOpen(false)}> <UserIcon /> Profile <CommandShortcut>⌘P</CommandShortcut> </CommandItem> <CommandItem onSelect={() => setOpen(false)}> <CreditCardIcon /> Billing <CommandShortcut>⌘B</CommandShortcut> </CommandItem> <CommandItem onSelect={() => setOpen(false)}> <SettingsIcon /> Settings <CommandShortcut>⌘S</CommandShortcut> </CommandItem> </CommandGroup> </CommandList> </CommandDialog> </> );}A ligação em código:
import { useEffect, useState } from 'react';
export function CommandPalette() {
const [open, setOpen] = useState(false);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((v) => !v);
}
};
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, []);
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Search docs, run commands..." />
<CommandList>
<CommandEmpty>No results.</CommandEmpty>
<CommandGroup heading="Navigate">
<CommandItem onSelect={() => goto('/inbox')}>
Inbox
<CommandShortcut>G I</CommandShortcut>
</CommandItem>
<CommandItem onSelect={() => goto('/projects')}>
Projects
<CommandShortcut>G P</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
);
}Padrão de Combobox
Combine Command com Popover para um picker buscável inline.
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" role="combobox" aria-expanded={open}>
{value || 'Select framework...'}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[240px] p-0">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((f) => (
<CommandItem key={f.value} value={f.value} onSelect={onSelect}>
{f.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>Busca no servidor
Desative a filtragem no cliente e dirija a lista a partir de uma query.
<Command shouldFilter={false}>
<CommandInput
value={query}
onValueChange={setQuery}
placeholder="Search..."
/>
<CommandList>
{loading && <CommandEmpty>Loading...</CommandEmpty>}
{!loading && results.length === 0 && (
<CommandEmpty>No results.</CommandEmpty>
)}
<CommandGroup>
{results.map((r) => (
<CommandItem key={r.id} onSelect={() => open(r.id)}>
{r.title}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>Acessibilidade
- Role: padrão ARIA combobox - o input é
role="combobox", a lista érole="listbox", os itens sãorole="option". - Teclado:
↑/↓movem entre os itens;Enterativa o item selecionado; digite para filtrar;Escfecha quando dentro deCommandDialog. - Gestão de foco: o foco fica retido no input; a seleção da lista é anunciada via
aria-selected(não movendo o foco). CommandDialogherda toda a acessibilidade doDialog(focus trap, modal, retorno de foco ao fechar).- Título e descrição: para o
CommandDialog, ambos ficam visualmente ocultos mas disponíveis a leitores de tela via RadixDialog. - Estado vazio:
CommandEmptyanuncia "No results" de forma polite para que usuários de tecnologia assistiva saibam que a busca não retornou nada. - Reduced motion: as animações do dialog respeitam
prefers-reduced-motion.
Relacionados
- Dialog - o modal base que o
CommandDialogenvolve. - Popover - host para o padrão de Combobox.
- Dropdown Menu - menu de ações mais simples, sem busca.