Command
Componente Command do shadcn - paleta de comandos, busca e navegação por teclado com o poder do cmdk.
Busque em todas as páginas da documentação
Componente Command do shadcn - paleta de comandos, busca e navegação por teclado com o poder do cmdk.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
npx shadcn@latest add command dialogimport {
Command, CommandDialog, CommandEmpty, CommandGroup,
CommandInput, CommandItem, CommandList, CommandSeparator,
} from "@/components/ui/command";
// Menu de comando inline
<Command>
<CommandInput placeholder="Pesquisar..." />
<CommandList>
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
<CommandGroup heading="Ações">
<CommandItem onSelect={() => console.log("novo")}>Novo Arquivo</CommandItem>
<CommandItem onSelect={() => console.log("salvar")}>Salvar</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Configurações">
<CommandItem>Perfil</CommandItem>
<CommandItem>Preferências</CommandItem>
</CommandGroup>
</CommandList>
</Command>Quando usar: Quando você deseja uma busca por teclado ou uma paleta de ações - navegação estilo Cmd+K, busca spotlight ou seleção de combobox.
"use client";
import { useEffect, useState, useCallback } from "react";
import { useRouter } from "next/navigation";
import {
CommandDialog, CommandEmpty, CommandGroup,
CommandInput, CommandItem, CommandList, CommandSeparator,
} from "@/components/ui/command";
import {
FileText, Settings, User, Search, Plus, Moon, Sun, LogOut,
} from "lucide-react";
type CommandAction = {
id: string;
label: string;
icon: React.ReactNode;
shortcut?: string;
action: () => void;
};
export function CommandPalette() {
const [open, setOpen] = useState(false);
const router = useRouter();
// Cmd+K para abrir
useEffect(() => {
function onKeyDown(e: KeyboardEvent) {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((o) => !o);
}
}
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, []);
const runAndClose = useCallback(
(fn: () => void) => {
fn();
setOpen(false);
},
[]
);
const pages: CommandAction[] = [
{ id: "home", label: "Home", icon: <FileText className="mr-2 h-4 w-4" />, action: () => router.push("/") },
{ id: "dashboard", label: "Dashboard", icon: <FileText className="mr-2 h-4 w-4" />, action: () => router.push("/dashboard") },
{ id: "settings", label: "Configurações", icon: <Settings className="mr-2 h-4 w-4" />, shortcut: "Cmd+,", action: () => router.push("/settings") },
{ id: "profile", label: "Perfil", icon: <User className="mr-2 h-4 w-4" />, action: () => router.push("/profile") },
];
const actions: CommandAction[] = [
{ id: "new-doc", label: "Novo Documento", icon: <Plus className="mr-2 h-4 w-4" />, shortcut: "Cmd+N", action: () => console.log("novo doc") },
{ id: "search", label: "Pesquisar Tudo", icon: <Search className="mr-2 h-4 w-4" />, shortcut: "Cmd+F", action: () => console.log("pesquisar") },
];
const theme: CommandAction[] = [
{ id: "light", label: "Modo Claro", icon: <Sun className="mr-2 h-4 w-4" />, action: () => document.documentElement.classList.remove("dark") },
{ id: "dark", label: "Modo Escuro", icon: <Moon className="mr-2 h-4 w-4" />, action: () => document.documentElement.classList.add("dark") },
];
return (
<>
{/* Botão de gatilho */}
<button
onClick={() => setOpen(true)}
className="flex items-center gap-2 rounded-lg border bg-muted/50 px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-muted"
>
<Search className="h-4 w-4" />
<span>Pesquisar...</span>
<kbd className="ml-4 rounded bg-muted px-1.5 py-0.5 text-xs font-mono">Cmd+K</kbd>
</button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Digite um comando ou pesquise..." />
<CommandList>
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
<CommandGroup heading="Páginas">
{pages.map((item) => (
<CommandItem key={item.id} onSelect={() => runAndClose(item.action)}>
{item.icon}
<span>{item.label}</span>
{item.shortcut && (
<kbd className="ml-auto text-xs text-muted-foreground">{item.shortcut}</kbd>
)}
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Ações">
{actions.map((item) => (
<CommandItem key={item.id} onSelect={() => runAndClose(item.action)}>
{item.icon}
<span>{item.label}</span>
{item.shortcut && (
<kbd className="ml-auto text-xs text-muted-foreground">{item.shortcut}</kbd>
)}
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Tema">
{theme.map((item) => (
<CommandItem key={item.id} onSelect={() => runAndClose(item.action)}>
{item.icon}
<span>{item.label}</span>
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Conta">
<CommandItem onSelect={() => runAndClose(() => console.log("logout"))}>
<LogOut className="mr-2 h-4 w-4" />
<span>Sair</span>
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
</>
);
}O que isso demonstra:
Cmd+K para abrirCommandDialog envolvendo (renderiza em um Dialog)onSelect que navegam ou acionam açõescmdk por Paco Coursey - um menu de comandos headless e composableCommandInput fornece uma caixa de busca que filtra automaticamente os filhos CommandItem pelo conteúdo de textoCommandItem é navegável por teclado - as teclas de seta se movem entre os itens, Enter selecionaCommandDialog combina Command com Dialog do shadcn para a sobreposição modalCommandGroup fornece agrupamento visual com um cabeçalho opcionalCommandEmpty renderiza quando nenhum item corresponde à consulta de buscaBusca assíncrona com carregamento:
function AsyncCommand() {
const [query, setQuery] = useState("");
const [results, setResults] = useState<Result[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!query) { setResults([]); return; }
setLoading(true);
const timer = setTimeout(async () => {
const data = await fetch(`/api/search?q=${query}`).then((r) => r.json());
setResults(data);
setLoading(false);
}, 300);
return () => clearTimeout(timer);
}, [query]);
return (
<Command shouldFilter={false}>
<CommandInput value={query} onValueChange={setQuery} />
<CommandList>
{loading && <CommandEmpty>Pesquisando...</CommandEmpty>}
{!loading && results.length === 0 && <CommandEmpty>Nenhum resultado.</CommandEmpty>}
{results.map((r) => (
<CommandItem key={r.id} value={r.id} onSelect={() => navigate(r)}>
{r.title}
</CommandItem>
))}
</CommandList>
</Command>
);
}Combobox (select com busca):
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
function Combobox({ options, value, onSelect }: {
options: { value: string; label: string }[];
value: string;
onSelect: (v: string) => void;
}) {
const [open, setOpen] = useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" role="combobox" aria-expanded={open}>
{options.find((o) => o.value === value)?.label ?? "Selecionar..."}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Pesquisar..." />
<CommandList>
<CommandEmpty>Nenhuma correspondência.</CommandEmpty>
<CommandGroup>
{options.map((opt) => (
<CommandItem key={opt.value} value={opt.value} onSelect={() => { onSelect(opt.value); setOpen(false); }}>
{opt.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}// CommandItem onSelect recebe o valor em minúsculas
<CommandItem
value="unique-id"
onSelect={(value: string) => {
// value é a versão em minúsculas do texto do item ou a prop value explícita
}}
>
// Tipando ações de comando
interface CommandAction {
id: string;
label: string;
icon: React.ReactNode;
shortcut?: string;
action: () => void;
keywords?: string[]; // termos de busca extras
}onSelect valor é em minúsculas - cmdk converte o valor para minúsculas para correspondência. Correção: Use uma prop value separada e procure os dados originais por ID.
shouldFilter={false} para assíncrono - Se você busca resultados de uma API, desabilite o filtro embutido. Correção: Defina shouldFilter={false} e trate a filtragem no lado do servidor.
Itens devem ter conteúdo de texto - cmdk filtra pelo conteúdo de texto de cada CommandItem. Se seu item tem apenas ícones, a filtragem não funcionará. Correção: Adicione texto visível ou use a prop value para filtragem personalizada.
z-index do Dialog - CommandDialog usa o mesmo z-index do Dialog. Se você tiver outras sobreposições, elas podem entrar em conflito. Correção: Ajuste o z-index via className no CommandDialog.
Conflitos de atalho de teclado - Cmd+K pode conflitar com atalhos do navegador ou editor. Correção: Verifique conflitos e documente o atalho para os usuários.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| cmdk (bare) | Você quer a biblioteca headless sem o estilo shadcn | Você já usa shadcn |
| Algolia DocSearch | Você precisa de busca full-text em documentação | Você precisa de uma paleta de comandos geral |
| kbar | Você quer uma paleta de comandos alternativa com API diferente | cmdk atende às suas necessidades |
<datalist> Nativo | Você precisa de um autocomplete simples nativo do navegador | Você quer navegação rica por teclado e agrupamento |
Command renderiza um menu de comandos inline incorporado na páginaCommandDialog envolve Command dentro de um Dialog do shadcn para uma sobreposição modalCommandDialog para paletas estilo Cmd+K; use Command para comboboxes inlineuseEffect(() => {
function onKeyDown(e: KeyboardEvent) {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((o) => !o);
}
}
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, []);cmdk por Paco Coursey -- uma biblioteca de menu de comandos headless e composablevalue separada em CommandItem e procure os dados originais por ID em vez de depender da string onSelectCommand dentro de um Popover em vez de um DialogPopoverTrigger como o botão e PopoverContent contendo o CommandsetOpen(false)CommandItemuseEffect(() => {
if (!query) { setResults([]); return; }
setLoading(true);
const timer = setTimeout(async () => {
const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
setResults(data);
setLoading(false);
}, 300);
return () => clearTimeout(timer);
}, [query]);CommandItem corresponde à consulta de busca atualinterface CommandAction {
id: string;
label: string;
icon: React.ReactNode;
shortcut?: string;
action: () => void;
keywords?: string[];
}CommandItemvalue para filtragem personalizadaconst runAndClose = useCallback((fn: () => void) => {
fn();
setOpen(false);
}, []);
<CommandItem onSelect={() => runAndClose(item.action)}>onSelect aceita um callback do tipo (value: string) => voidvalue é o conteúdo de texto em minúsculas ou a prop value explícitaRevisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥