Overlays
Command
Paleta de comandos teclado-first — ⌘K floating ou inline (Combobox).
Overview
Command é uma paleta de comandos construída sobre
cmdk. Tem dois modos
canônicos:
CommandDialog— paleta flutuante ⌘K (GitHub, Linear, Vercel, Notion). Aberto por atalho de teclado, foco no input automático.Commandinline — usado dentro de Popover para o padrão Combobox (input com busca + lista filtrada).
Command é teclado-first: o valor não é discoverability (ninguém vai "explorar" os comandos), é velocidade de targeting para usuários recorrentes. Não use para usuários novos.
Preview
Command inline com grupos e shortcuts.
Nenhum resultado.
Agenda
Buscar emoji
Calculadora
Perfil⌘P
Faturamento⌘B
Preferências⌘S
Anatomy
<Command> ← root (cmdk Command)
├─ <CommandInput /> ← busca com SearchIcon embutido
└─ <CommandList>
├─ <CommandEmpty /> ← mostrado quando filter retorna 0
├─ <CommandGroup heading>
│ ├─ <CommandItem /> ← um por entrada
│ └─ <CommandShortcut /> (opcional, dentro do Item)
└─ <CommandSeparator />
</Command>
<CommandDialog> ← envelope que injeta Dialog ao redor
└─ ... (mesmo conteúdo)
</CommandDialog>Usage
"use client";
import * as React from "react";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut
} from "@kalvner/kds/overlays/command";
export function Palette() {
const [open, setOpen] = React.useState(false);
React.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="Type a command or search... ⌘K" />
<CommandList>
<CommandEmpty>No results.</CommandEmpty>
<CommandGroup heading="Navigation">
<CommandItem onSelect={() => navigate("/inbox")}>
Inbox
<CommandShortcut>G I</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
);
}Props
Command (Root) e CommandDialog
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
value / onValueChange | string / (v) => void | — | Item selecionado (para controle externo). |
filter | (value, search) => number | — | Custom filter (default: cmdk substring). |
loop | boolean | false | ↑/↓ dão wrap. |
CommandDialog herda todos os props de Dialog, além
de title/description (sr-only por padrão).
CommandInput
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
placeholder | string | — | Inclua sempre uma dica de teclado. |
value / onValueChange | string / (v) => void | — | Controlado opcional. |
CommandItem
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
value | string | (texto do filho) | Override do que o filtro busca. |
onSelect | (value: string) => void | — | Disparado ao Enter ou clique. |
disabled | boolean | false | Bloqueia este item. |
Subcomponents
CommandInput— input comSearchIconembutido.CommandList— wrapper rolável (max-h-[300px]).CommandEmpty— render condicional automático em zero resultados.CommandGroup— grupo com heading sticky.CommandItem— entrada selecionável; aceita ícone + texto + shortcut.CommandShortcut—<span>posicionado à direita com tracking.CommandSeparator— divisor de 1px.CommandDialog— envolve tudo num Dialog com header sr-only.
Variants
Inline (sem dialog)
Usado dentro de Popover para Combobox.
Nenhum.
Next.js
SvelteKit
Astro
States
| Estado | Comportamento |
|---|---|
Item data-selected=true | Background accent, foreground accent-foreground. |
Item data-disabled=true | Pointer-events none, opacity 50%. |
| Empty (filter=0) | CommandEmpty aparece em vez dos grupos. |
Composition
Combobox pattern
Command inline dentro de Popover é o padrão Combobox
canônico (autocomplete acessível). O trigger é um botão; ao abrir,
foco vai para o CommandInput.
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">
{value ? value : "Selecione um framework"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[280px] p-0">
<Command>
<CommandInput placeholder="Buscar..." />
<CommandList>
<CommandEmpty>Nada encontrado.</CommandEmpty>
<CommandGroup>
{options.map((opt) => (
<CommandItem key={opt} onSelect={(v) => setValue(v)}>
{opt}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>⌘K palette
Veja Usage acima. A regra: um único atalho global por app (não pulverize por feature).
When to use
- App com 5+ páginas onde usuários recorrentes querem pular sem clicar.
- Combobox com lista grande (>10 itens) onde busca é necessária.
- Switcher de workspace / project / org.
- Editor com paleta de comandos (Notion, Linear).
When not to use
- App pequeno (menos de 5 destinos) — atalho não amortiza descoberta.
- Lista com 5 itens ou menos sem busca útil — use Select ou DropdownMenu.
- Usuários novos só — eles não vão descobrir ⌘K.
- Mobile-only — ⌘K não tem equivalente touch fluido.
Best practices
- Sempre inclua dica de teclado no placeholder ("Type a command... ⌘K").
- Agrupe semanticamente — Navigation, Actions, Recent. Sem grupo, vira lista plana e perde o ponto.
- Mostre Recent no topo (estado serializado por usuário) — top-of-mind vence ordem alfabética.
- Não exceda ~30 itens visíveis sem filtro — depois disso, o usuário nunca scrolla, só busca.
- Em mobile, prefira Drawer com lista — ⌘K é desktop-pattern.
Accessibility
| Concern | Comportamento |
|---|---|
| Combobox | cmdk aplica role="combobox" no input com aria-controls e aria-expanded. |
| Listbox | A lista é role="listbox", items são role="option". |
| Keyboard | ↑/↓ navegam, Enter seleciona, Esc fecha (no Dialog). |
| Focus | CommandDialog move foco pro input ao abrir; restaura ao fechar. |
| Title | CommandDialog requer title (sr-only ok). |
| Live filter | CommandEmpty é anunciado ao mudar de "tem resultados" para "não tem". |