Skip to main content
Gremorie
Overlays

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 grupos

Instalaçã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:

PropTypeDefaultDescription
valuestring-Valor do item selecionado controlado.
onValueChange(value: string) => void-Disparado quando a seleção muda via setas ou input.
shouldFilterbooleantrueDesative para controlar a filtragem você mesmo (ex.: busca no servidor).
filter(value: string, search: string, keywords?: string[]) => numberdefault fuzzy matchScoring de filtro customizado (retorne 0 para ocultar).
loopbooleanfalseFaz 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.

PropTypeDefaultDescription
valuestring-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.

PropTypeDefaultDescription
headingstring | ReactNode-Título de seção acima dos itens.

Estende o Command.Group do cmdk.

<CommandItem>

PropTypeDefaultDescription
valuestringtext contentValor interno usado para filtragem e seleção.
onSelect(value: string) => void-Disparado quando o item é ativado (Enter ou clique).
disabledbooleanfalseDesabilita a interação; renderiza a 50% de opacidade.
keywordsstring[][]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.

PropTypeDefaultDescription
titlestring"Command Palette"Título para leitor de tela (visualmente oculto).
descriptionstring"Search for a command to run..."Descrição para leitor de tela (visualmente oculta).
showCloseButtonbooleantrueRenderiza o botão X de fechar no dialog.

Todas as demais props do Dialog (open, onOpenChange, etc.) são encaminhadas.

Composição

  1. <Command> detém o estado de busca e a lógica de filtragem.
  2. <CommandInput> fica no topo como a busca controlada.
  3. <CommandList> guarda os resultados.
  4. Dentro da lista: CommandEmpty (estado sem resultados) seguido de um ou mais blocos CommandGroup. CommandSeparator divide os grupos.
  5. Itens: cada CommandItem expõe onSelect; adicione CommandShortcut para 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ão role="option".
  • Teclado: / movem entre os itens; Enter ativa o item selecionado; digite para filtrar; Esc fecha quando dentro de CommandDialog.
  • Gestão de foco: o foco fica retido no input; a seleção da lista é anunciada via aria-selected (não movendo o foco).
  • CommandDialog herda toda a acessibilidade do Dialog (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 Radix Dialog.
  • Estado vazio: CommandEmpty anuncia "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 CommandDialog envolve.
  • Popover - host para o padrão de Combobox.
  • Dropdown Menu - menu de ações mais simples, sem busca.

On this page