HextaUI

Command

Uma lista pesquisável de ações, inline ou como paleta ⌘K, com páginas, atalhos e correspondências destacadas.

import {
  IconCalculator,
  IconCalendar,
  IconCreditCard,
  IconMoodSmile,
  IconSettings,
  IconUser,
} from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/components/ui/command"

export function CommandDemo() {
  return (
    <Command highlight className="w-full max-w-sm">
      <CommandInput placeholder="Type a command or search…" />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="Suggestions">
          <CommandItem>
            <IconCalendar />
            Calendar
          </CommandItem>
          <CommandItem>
            <IconMoodSmile />
            Search Emoji
          </CommandItem>
          <CommandItem disabled>
            <IconCalculator />
            Calculator
          </CommandItem>
        </CommandGroup>
        <CommandSeparator />
        <CommandGroup heading="Settings">
          <CommandItem shortcut="mod+p">
            <IconUser />
            Profile
          </CommandItem>
          <CommandItem shortcut="mod+b">
            <IconCreditCard />
            Billing
          </CommandItem>
          <CommandItem shortcut="mod+,">
            <IconSettings />
            Settings
          </CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/command.json

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/components/ui/command"
<Command>
  <CommandInput placeholder="Type a command or search…" />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Suggestions">
      <CommandItem onSelect={() => openCalendar()}>Calendar</CommandItem>
      <CommandItem shortcut="mod+p">Profile</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>

Os atalhos usam mod para ⌘ em dispositivos Apple e Ctrl em todos os outros. Os rótulos são formatados por plataforma para você.

Command
├── CommandInput
├── CommandList
│   ├── CommandEmpty
│   ├── CommandLoading
│   ├── CommandGroup
│   │   └── CommandItem
│   │       └── CommandShortcut
│   ├── CommandSeparator
│   └── CommandPage
│       └── CommandGroup
└── CommandFooter

CommandDialog
└── Command

Básico

Digitar filtra e classifica os itens conforme você escreve. Grupos sem correspondências desaparecem, e a altura da lista é animada para se ajustar ao que sobra.

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from "@/components/ui/command"

export function CommandBasic() {
  return (
    <Command className="w-full max-w-sm">
      <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>
  )
}

Dialog

Coloque um <Command /> dentro de <CommandDialog /> e alterne-o com useCommandHotkey. Pressione ⌘K ou Ctrl K. Os atalhos dos itens funcionam enquanto ele está aberto, as correspondências são destacadas e preserveSearch mantém a consulta e a seleção para a próxima vez que abrir.

"use client"

import * as React from "react"
import {
  IconCalculator,
  IconCalendar,
  IconCheck,
  IconCreditCard,
  IconExternalLink,
  IconMoodSmile,
  IconPalette,
  IconPoint,
  IconSettings,
  IconUser,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandFooter,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandPage,
  CommandSeparator,
  CommandShortcut,
  useCommandHotkey,
} from "@/components/ui/command"

export function CommandDialogDemo() {
  const [open, setOpen] = React.useState(false)
  const [last, setLast] = React.useState<string>()
  const [theme, setTheme] = React.useState("System")

  useCommandHotkey("mod+k", () => setOpen((value) => !value))

  const run = (action: string) => {
    setLast(action)
    setOpen(false)
  }

  return (
    <div className="flex flex-col items-center gap-3">
      <Button variant="outline" onClick={() => setOpen(true)}>
        Open palette
        <CommandShortcut hotkey="mod+k" />
      </Button>
      <p className="text-sm text-muted-foreground">
        {last ? `Ran “${last}”.` : "Nothing run yet."} Theme: {theme}.
      </p>
      <CommandDialog open={open} onOpenChange={setOpen} preserveSearch>
        <Command highlight>
          <CommandInput placeholder="Type a command or search…" />
          <CommandList>
            <CommandEmpty>
              {(search) => `No results for “${search}”.`}
            </CommandEmpty>
            <CommandGroup heading="Suggestions">
              <CommandItem
                shortcut="mod+shift+c"
                onSelect={() => run("Calendar")}
              >
                <IconCalendar />
                Calendar
              </CommandItem>
              <CommandItem onSelect={() => run("Search Emoji")}>
                <IconMoodSmile />
                Search Emoji
              </CommandItem>
              <CommandItem disabled>
                <IconCalculator />
                Calculator
              </CommandItem>
              <CommandItem page="theme" pageTitle="Theme">
                <IconPalette />
                Change theme…
              </CommandItem>
            </CommandGroup>
            <CommandSeparator />
            <CommandGroup heading="Settings">
              <CommandItem shortcut="mod+p" onSelect={() => run("Profile")}>
                <IconUser />
                Profile
              </CommandItem>
              <CommandItem shortcut="mod+b" onSelect={() => run("Billing")}>
                <IconCreditCard />
                Billing
              </CommandItem>
              <CommandItem
                shortcut="mod+,"
                keywords={["preferences", "options"]}
                onSelect={() => run("Settings")}
              >
                <IconSettings />
                Settings
              </CommandItem>
            </CommandGroup>
            <CommandSeparator />
            <CommandGroup heading="Links">
              <CommandItem href="/docs" onSelect={() => setOpen(false)}>
                <IconExternalLink />
                All components
              </CommandItem>
            </CommandGroup>
            <CommandPage id="theme">
              <CommandGroup heading="Theme">
                {["Light", "Dark", "System"].map((option) => (
                  <CommandItem
                    key={option}
                    onSelect={() => {
                      setTheme(option)
                      run(`Theme: ${option}`)
                    }}
                  >
                    {option === theme ? <IconCheck /> : <IconPoint />}
                    {option}
                  </CommandItem>
                ))}
              </CommandGroup>
            </CommandPage>
          </CommandList>
          <CommandFooter />
        </Command>
      </CommandDialog>
    </div>
  )
}

Páginas

Um item com page abre o <CommandPage /> correspondente. O título da página aparece como um chip no input, a lista desliza pelo lado, e Backspace em uma busca vazia ou Escape volta.

"use client"

import * as React from "react"
import {
  IconBrandGithub,
  IconFolder,
  IconFolderPlus,
  IconUsers,
} from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandPage,
} from "@/components/ui/command"

const projects = ["hextaui", "marketing-site", "design-tokens"]
const members = ["Ada Lovelace", "Grace Hopper", "Alan Turing"]

export function CommandPages() {
  const [last, setLast] = React.useState<string>()

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Command>
        <CommandInput placeholder="Search…" />
        <CommandList>
          <CommandEmpty>No results found.</CommandEmpty>
          <CommandGroup heading="Workspace">
            <CommandItem page="projects" pageTitle="Projects">
              <IconFolder />
              Projects
            </CommandItem>
            <CommandItem page="members" pageTitle="Members">
              <IconUsers />
              Members
            </CommandItem>
            <CommandItem onSelect={() => setLast("New project")}>
              <IconFolderPlus />
              New project
            </CommandItem>
          </CommandGroup>
          <CommandPage id="projects">
            <CommandGroup heading="Projects">
              {projects.map((project) => (
                <CommandItem key={project} onSelect={setLast}>
                  <IconBrandGithub />
                  {project}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandPage>
          <CommandPage id="members">
            <CommandGroup heading="Members">
              {members.map((member) => (
                <CommandItem key={member} onSelect={setLast}>
                  <IconUsers />
                  {member}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandPage>
        </CommandList>
      </Command>
      <p className="text-sm text-muted-foreground">
        {last ? `Selected “${last}”.` : "Nothing selected yet."}
      </p>
    </div>
  )
}

Rolável

Listas longas rolam dentro de uma altura limitada. O item selecionado sempre é mantido à vista enquanto você se move pelo teclado.

import { IconFileText } from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command"

const documents = Array.from({ length: 60 }, (_, index) => ({
  id: index + 1,
  title: `Document ${String(index + 1).padStart(2, "0")}`,
}))

export function CommandScrollable() {
  return (
    <Command className="w-full max-w-sm">
      <CommandInput placeholder="Search 60 documents…" />
      <CommandList>
        <CommandEmpty>No documents match.</CommandEmpty>
        <CommandGroup heading="Documents">
          {documents.map((doc) => (
            <CommandItem key={doc.id}>
              <IconFileText />
              {doc.title}
            </CommandItem>
          ))}
        </CommandGroup>
      </CommandList>
    </Command>
  )
}

Resultados assíncronos

Defina shouldFilter={false} e renderize os resultados que você buscar. <CommandLoading /> espera 150 ms antes de aparecer e então permanece por pelo menos 300 ms, de modo que respostas rápidas nunca exibem um spinner em flash. Experimente as duas latências.

"use client"

import * as React from "react"
import { IconUser } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandLoading,
  useCommandLoading,
} from "@/components/ui/command"

const people = [
  "Ada Lovelace",
  "Alan Turing",
  "Grace Hopper",
  "Katherine Johnson",
  "Linus Torvalds",
  "Margaret Hamilton",
  "Tim Berners-Lee",
]

export function CommandAsync() {
  const [query, setQuery] = React.useState("")
  const [results, setResults] = React.useState(people)
  const [loading, setLoading] = React.useState(false)
  const [latency, setLatency] = React.useState(700)
  const pending = useCommandLoading(loading)
  const timerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined)

  React.useEffect(() => () => clearTimeout(timerRef.current), [])

  const search = (nextQuery: string, nextLatency: number) => {
    clearTimeout(timerRef.current)
    setLoading(true)
    timerRef.current = setTimeout(() => {
      setResults(
        people.filter((person) =>
          person.toLowerCase().includes(nextQuery.toLowerCase())
        )
      )
      setLoading(false)
    }, nextLatency)
  }

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <div className="flex gap-2">
        {[80, 700].map((ms) => (
          <Button
            key={ms}
            size="sm"
            variant={latency === ms ? "secondary" : "outline"}
            onClick={() => {
              setLatency(ms)
              search(query, ms)
            }}
          >
            {ms} ms
          </Button>
        ))}
      </div>
      <Command shouldFilter={false} highlight>
        <CommandInput
          placeholder="Search people…"
          value={query}
          onValueChange={(next) => {
            setQuery(next)
            search(next, latency)
          }}
        />
        <CommandList>
          <CommandLoading loading={loading}>Searching…</CommandLoading>
          <CommandEmpty>
            {(value) => `No people match “${value}”.`}
          </CommandEmpty>
          {pending ? null : (
            <CommandGroup heading="People">
              {results.map((person) => (
                <CommandItem key={person}>
                  <IconUser />
                  {person}
                </CommandItem>
              ))}
            </CommandGroup>
          )}
        </CommandList>
      </Command>
    </div>
  )
}

Conteúdo longo

Os títulos quebram de linha, nomes longos são truncados ou quebrados como você preferir, e os atalhos nunca são empurrados para fora.

import { IconFileText } from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandShortcut,
} from "@/components/ui/command"

export function CommandLongContent() {
  return (
    <Command className="w-full max-w-72">
      <CommandInput placeholder="Search…" />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="A group heading that is long enough to wrap onto two lines">
          <CommandItem>
            <IconFileText />
            <span className="min-w-0 truncate">
              quarterly-planning-final-final-v2-reviewed-by-legal.pdf
            </span>
            <CommandShortcut>⌘⇧O</CommandShortcut>
          </CommandItem>
          <CommandItem>
            <IconFileText />
            <span className="min-w-0 wrap-anywhere">
              averyveryverylongunbrokenfilenamethatshouldwrapinsteadofescaping.txt
            </span>
          </CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  )
}

Da direita para a esquerda

Os ícones, os atalhos, o chip da página e o deslize da página seguem a direção de leitura.

import { IconCalendar, IconSettings } from "@tabler/icons-react"

import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandShortcut,
} from "@/components/ui/command"

export function CommandRtl() {
  return (
    <div dir="rtl" className="w-full max-w-sm">
      <Command dir="rtl">
        <CommandInput placeholder="ابحث عن أمر…" />
        <CommandList>
          <CommandEmpty>لا توجد نتائج.</CommandEmpty>
          <CommandGroup heading="اقتراحات">
            <CommandItem>
              <IconCalendar />
              التقويم
              <CommandShortcut>⌘T</CommandShortcut>
            </CommandItem>
            <CommandItem>
              <IconSettings />
              الإعدادات
              <CommandShortcut>⌘S</CommandShortcut>
            </CommandItem>
          </CommandGroup>
        </CommandList>
      </Command>
    </div>
  )
}
TeclaAção
↓Seleciona o próximo item.
↑Seleciona o item anterior.
Alt↓Salta para o primeiro item do próximo grupo.
Alt↑Salta para o primeiro item do grupo anterior.
HomeSeleciona o primeiro item.
EndSeleciona o último item.
CtrlNSeleciona o próximo item. Ctrl J também funciona. Desative com vimBindings.
CtrlPSeleciona o item anterior. Ctrl K também funciona. Desative com vimBindings.
EnterExecuta o item selecionado. Em um item de link, ⌘ Enter ou Ctrl Enter o abre em uma nova aba.
EscPrimeiro limpa a busca, depois volta uma página e depois fecha o diálogo.
BackspaceVolta uma página quando a busca está vazia.
⌘PQualquer atalho de item executa o seu item enquanto o foco está dentro do menu de comandos.
  • O input é um combobox que aponta para o item selecionado, então os leitores de tela anunciam cada item conforme você se move.
  • Uma região live educada (polite) anuncia o número de resultados logo depois que você para de digitar e anuncia o título da página ao abrir ou sair de uma página. Altere o texto com formatResults e rootTitle.
  • <CommandDialog /> tem título e descrição ocultos, prende o foco enquanto aberto e o devolve ao gatilho ao fechar.
  • Os atalhos dos itens são expostos com aria-keyshortcuts.
  • Com movimento reduzido ativado, os itens executam sem o piscar de confirmação e as páginas aparecem com fade em vez de deslizar.

Construído sobre o cmdk, com <CommandDialog /> no diálogo do Base UI. As partes aceitam as props da parte do cmdk que envolvem.

PropTipoPadrão
labelNome acessível do menu.
string"Command menu"
highlightDestaca as letras correspondentes em cada item e atenua o resto.
booleanfalse
shouldFilterDefina como false para filtrar e ordenar os itens você mesmo, por exemplo quando os resultados vêm de um servidor.
booleantrue
filterRetorna uma pontuação de 0 (oculto) a 1 (melhor correspondência).
(value: string, search: string, keywords?: string[]) => number–
valueO valor do item selecionado.
string–
defaultValue
string–
onValueChange
(value: string) => void–
loopVolta ao início nas pontas da lista.
booleanfalse
vimBindingsNavegação com Ctrl N, J, P e K.
booleantrue
disablePointerSelection
booleanfalse
formatResultsTexto anunciado aos leitores de tela após a digitação.
(count: number) => string"3 results"
rootTitleAnunciado quando você sai da última página e volta à raiz.
string"All commands"
AtributoDescrição
data-slot="command"Selecione a raiz no CSS.
data-highlightingPresente enquanto highlight está ativado e a busca não está vazia.
--command-radiusRaio externo. Os itens derivam dele um raio concêntrico.
--command-insetPadding entre a borda da lista e seus itens.
PropTipoPadrão
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean, details) => void–
preserveSearchMantém o diálogo montado para que a consulta, a página e a seleção sobrevivam ao fechamento. A consulta fica selecionada quando ele reabre.
booleanfalse
titleTítulo do diálogo visualmente oculto.
string"Command menu"
descriptionDescrição do diálogo visualmente oculta.
string"Search for a command to run."
showCloseButton
booleanfalse
classNameAplicado ao popup do diálogo.
string–
AtributoDescrição
data-slot="command-dialog"O popup do diálogo.
data-slot="command-dialog-overlay"O pano de fundo.
data-openPresente no popup enquanto está aberto.
PropTipoPadrão
valueTexto de busca controlado.
string–
onValueChange
(search: string) => void–
placeholder
string–
clearLabelNome acessível do botão de limpar.
string"Clear search"
backLabelNome acessível do chip da página.
(title: string) => string(title) => `Back from ${title}`
AtributoDescrição
data-slot="command-input"O input.
data-slot="command-input-wrapper"A linha que contém o ícone, o input e o botão de limpar.
data-slot="command-clear"O botão de limpar, exibido depois que você digita.
data-slot="command-page-chip"O chip de voltar exibido em uma página.
PropTipoPadrão
labelNome acessível da lista.
string–
AtributoDescrição
data-slot="command-list"A lista.
data-settledPresente depois que a lista se mediu. A transição de altura só roda enquanto está definido.
--cmdk-list-heightAltura dos itens visíveis, usada para animar a lista.
PropTipoPadrão
childrenUse a forma de função para repetir a consulta.
ReactNode | (search: string) => ReactNode–
AtributoDescrição
data-slot="command-empty"Oculto enquanto um CommandLoading está na lista.
PropTipoPadrão
loading
booleantrue
delayMilissegundos de espera antes de o spinner aparecer.
number150
minDurationMínimo de milissegundos que o spinner permanece depois de exibido.
number300
labelRótulo acessível. O padrão são os children em string.
string–
progress
number–
AtributoDescrição
data-slot="command-loading"A linha de carregamento.
data-pendingPresente durante o delay, enquanto a linha é anunciada mas ainda não está visível.
PropTipoPadrão
heading
ReactNode–
valueObrigatório quando não há heading.
string–
forceMountMantém o grupo visível durante a filtragem.
booleanfalse
AtributoDescrição
data-slot="command-group"O grupo.
[cmdk-group-heading]O elemento de heading.
PropTipoPadrão
onSelectExecuta ao clicar, com Enter ou pelo atalho do item, depois do piscar de confirmação.
(value: string) => void–
valueUsado na filtragem. O padrão é o texto do item, sem o atalho.
string–
keywordsPalavras extras que correspondem a este item.
string[]–
disabled
booleanfalse
shortcutUm atalho de teclado como "mod+shift+c". Exibido no item e o executa enquanto o foco está no menu.
string–
pageAbre o CommandPage com este id em vez de executar.
string–
pageTitleTítulo exibido no chip da página. O padrão é o value.
string–
hrefRenderiza o item como um link. Enter o segue, ⌘ ou Ctrl Enter abre uma nova aba.
string–
renderUm elemento de link para renderizar no lugar, como o <Link /> do Next.js.
ReactElement–
confirmPisca o item brevemente antes de executá-lo, para que a escolha seja percebida.
booleantrue
forceMountMantém o item visível durante a filtragem.
booleanfalse
AtributoDescrição
data-slot="command-item"O item.
data-selected="true"Presente no item selecionado.
data-disabled="true"Presente em itens desativados.
data-valueO valor usado para filtragem.
data-confirmingPresente durante o piscar de confirmação.
data-pagePresente em itens que abrem uma página.
PropTipoPadrão
idCorresponde à prop page do item que a abre. Seus grupos e itens só são renderizados enquanto ela é a página atual.
string–
PropTipoPadrão
hotkeyFormata um atalho como "mod+k" para a plataforma atual. Os children o substituem.
string–
AtributoDescrição
data-slot="command-shortcut"O rótulo do atalho.
PropTipoPadrão
alwaysRenderMantém visível durante a busca.
booleanfalse
AtributoDescrição
data-slot="command-separator"O separador.
PropTipoPadrão
childrenO padrão são dicas de teclas que se atualizam em uma página. Oculto em telas sensíveis ao toque.
ReactNode–
AtributoDescrição
data-slot="command-footer"O rodapé.
const [open, setOpen] = React.useState(false)

useCommandHotkey("mod+k", () => setOpen((value) => !value))
PropTipoPadrão
hotkeyEscutado no documento inteiro. Atalhos sem modificador são ignorados enquanto você digita em um campo.
string–
callback
(event: KeyboardEvent) => void–
options.enabled
booleantrue

Retorna se um indicador de carregamento deve estar visível, com o mesmo delay e duração mínima de <CommandLoading />. Use para ocultar resultados desatualizados enquanto uma requisição está em andamento.

PropTipoPadrão
loading
boolean–
options.delay
number150
options.minDuration
number300
  • useCommandPages() retorna { pages, page, push, pop, reset } para controlar as páginas a partir do seu código.
  • useCommandState(selector) lê o estado do cmdk, como a busca ou a contagem filtrada.
  • useHotkeyLabel(hotkey) formata um atalho para a plataforma atual, como ⌘K ou Ctrl+K.

Usado em blocos

Blocos que se baseiam em Command.