Pular para o conteúdo
Menus & seleção

Componentes

Menus & seleção

Uma pergunta decide tudo aqui: escolher ou agir? Escolher 1 entre N pinta o escolhido de verde e mantém a lista viva; agir só destaca no hover e fecha o menu. Mesma peça técnica, duas gramáticas — trocá-las faz a pessoa achar que desfez uma escolha quando na verdade executou uma ação.
src/components/ui/dropdown-menu.tsxradix-uianimate-popup-in

Menu de ação

O kebab de uma linha, o “mais” de uma toolbar. Ninguém fica selecionado depois — o menu executa e fecha.
← kebab de linha · menu da toolbar
DropdownMenu real · item destaca no hover · destrutivo em vermelho, sempre por último e depois de um separador
o uso
<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline" size="icon-sm" aria-label="Ações de Marina Alvez">
      <EllipsisVertical />
    </Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent align="end">
    <DropdownMenuItem onSelect={editar}><Pencil /> Editar cadastro</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem variant="destructive" onSelect={excluir}>
      <Trash2 /> Excluir
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
PropTipoPadrãoO que faz
DropdownMenuContent sideOffsetnumber6A distância do painel até o gatilho. 6px é o respiro do canon; não mexer sem motivo.
DropdownMenuContent align"start" | "center" | "end""center"Menu de linha alinha por start; menu de toolbar à direita alinha por end, pra não vazar da tela.
DropdownMenuItem variant"default" | "destructive""default"O destrutivo pinta texto e ícone de vermelho e usa focus:bg-destructive/10.
DropdownMenuItem onSelect(e) => voidPreferir a onClick: cobre teclado e Enter, e o Radix fecha o menu sozinho.
PeçaReceita
Painelrounded-xl border bg-surface p-1 shadow-md ring-1 ring-foreground/5
Entradaanimate-popup-in (100ms) com origin vindo do Radix — o zoom nasce do canto por onde abriu
Saídaseca, sem animação
Itemrounded-md px-2 py-1.5 text-sm · ícone size-4 em fg-subtle
Item em focofocus:bg-ink-50 (é o hover E o teclado — o Radix usa focus)
Separadormy-1 h-px bg-border
Largura mínimamin-w-[8rem]

Armadilha já pagaFechar por código não avisa

Fechar um Radix Dialog ou menu programaticamente NÃO dispara onOpenChange — o estado da última abertura vaza pra próxima. Remonte com uma key por abertura quando o conteúdo depender do que estava aberto.

Escolher 1 entre N

Filtro, ordenação, troca de visão. O escolhido fica verde e a contagem mora à direita — a lista continua fazendo sentido depois de fechada.
← o item escolhido pinta de verde
mesma peça, outra gramática de item: check verde + bg-brand-muted no escolhido

Sim

✓ Odontologia 34

Dermatologia 27

Escolha: o item ativo é verde e permanece. A contagem alinha à direita, tabular.

Não

Excluir paciente

Ver histórico

Ação pintada de verde parece escolha feita: quem lê acha que “Excluir paciente” é o estado atual, não o que vai acontecer no clique.

Quando NÃO usar menu pra escolher

Nem toda escolha merece painel flutuante.
SituaçãoUse
Escolha dentro de um formulárioSelectField (select nativo) — no celular ele abre a roleta do sistema
Duas ou três opções, sempre visíveisGrupo de pílulas com aria-pressed, ou abas se forem LUGARES
Muitas opções, e é preciso BUSCAR pra acharPicker em diálogo — nunca um campo de busca dentro do menu (ver abaixo)
Ligar/desligarCheckbox — não é escolha entre opções
dentro de formulário: select nativo, sem painel custom
lista longa: busca ANTES do menu, filtrando a lista da tela

Quando é preciso buscar pra achar

O sinal não é a quantidade de opções: é precisar procurar. Trinta serviços ainda cabem num select; três mil pacientes, não. E aí a resposta do produto é um PICKER EM DIÁLOGO — nunca um campo de busca dentro de um menu.
A escolha éPeçaMolde
Entre poucas opções, dentro de um formulárioSelectField (select nativo)No celular abre a roleta do sistema; funciona no teclado sozinho e não custa JS.
Entre muitas, e achar já é metade da tarefaDialog com busca dentro (picker)ajustes/precificacao/insumo-picker-dialog.tsx — buscar → tocar no item → informar a quantidade → adicionar.
Sobre a lista inteira da telaSearchInput na toolbar + chips de recorteFiltra o que já está na tela; não escolhe um valor pra um campo.

NuncaCampo de busca dentro de menu: recusado — 23/08/2026

O “SearchSelect” (menu flutuante com um campo de busca no topo) esteve desenhado neste guia e não vai existir. O motivo está escrito no código do picker que o substituiu: a versão anterior dele era um campo com datalist e “parecia campo de login — o 1Password pulava em cima”.

Campo de texto sem rótulo dentro de um painel flutuante é exatamente a assinatura que um gestor de senha procura. O picker em diálogo desarma isso: o valor escolhido viaja como uuid num campo escondido, e a busca leva data-1p-ignore + autoComplete="off" por garantia.

Teclado e acessibilidade

O Radix entrega quase tudo; o que sobra é não estragar.
TeclaO que faz
Enter / Espaço no gatilhoAbre e põe o foco no primeiro item
↑ ↓Anda pelos itens (o item em foco usa focus:bg-ink-50)
Digitar uma letraPula pro item que começa com ela
EscFecha e devolve o foco pro gatilho
TabFecha o menu e segue o fluxo da página

O gatilho usa asChild pra virar o Button do canon — assim ele herda a pílula, o anel de foco e o crescimento no dedo, sem aninhar botão dentro de botão. Gatilho só de ícone precisa de aria-label com o objeto: “Ações de Marina Alvez”, não “Ações”.