Pular para o conteúdo
Diálogos & confirmação

Componentes

Diálogos & confirmação

Três peças com pesos diferentes: o Dialog pra tarefa curta que precisa de foco, o ConfirmDialog pro sim/não que interrompe, e a tarja pra destruição dentro de uma lista — a que substituiu o duplo clique depois de ele falhar duas vezes no mesmo teste.
ui/dialog.tsxui/confirm-dialog.tsxui/confirm-strip.tsx

Dialog

Tarefa curta que precisa de foco total: criar um agendamento, escolher profissionais, editar marcadores. Se a tarefa tem mais de cinco campos, ela merece uma tela.
Radix Dialog · painel surface rounded-xl + shadow-md · overlay bg-(--scrim) sem animação
PeçaReceitaNota
Painelmax-w-md · max-h-[85dvh] overflow-y-auto · rounded-xl border bg-surface p-6 shadow-md ring-1 ring-foreground/5Mesma receita do DropdownMenu: o que flutua tem a mesma pele.
Largura no celularw-[calc(100%-2rem)]16px de respiro de cada lado, sempre.
Entradaanimate-popup-in + motion-reduce:animate-none100ms de fade + zoom.
Overlaybg-(--scrim)Escurece SEM animação (estados calmos). No escuro o véu sobe pra 65%, porque tinta sobre fundo escuro não escurece nada.
Títulotext-base font-semiboldDialogTitle é obrigatório: é o nome acessível do modal.
Descriçãomt-1 text-sm text-fg-mutedUma frase. Se precisa de duas, repense a tarefa.
Fecharbotão redondo no canto, size-8, focus-ringAlém do Esc e do clique fora, que o Radix já entrega.
Botõesflex-col-reverse gap-2 sm:flex-row sm:justify-endNo celular empilham com a ação principal EMBAIXO, ao alcance do polegar.
<Dialog open={aberto} onOpenChange={setAberto}>
  <DialogTrigger asChild><Button variant="outline">Abrir</Button></DialogTrigger>
  <DialogContent>
    <DialogTitle>Novo agendamento</DialogTitle>
    <DialogDescription>O que este diálogo faz, em uma frase.</DialogDescription>
    {/* corpo */}
  </DialogContent>
</Dialog>

Armadilha já pagaRemonte com key quando fechar por código

Fechar o Radix Dialog programaticamente não dispara onOpenChange: o estado da última abertura vaza pra próxima. Se o conteúdo depende do que estava aberto, use uma key por abertura.

ConfirmDialog

Sim/não sobre o Dialog, pra quando a ação parte de fora de uma lista — um botão de tela, um item de menu.
max-w-sm · confirmar fecha sozinho · no celular os botões empilham (principal embaixo)
PropTipoPadrãoO que faz
open / onOpenChangeboolean / (b) => voidControlado pelo chamador.
titleReactNodePergunta com o objeto dentro: “Remover o número (11) 91234-5678?”.
descriptionReactNodeO que acontece, e o que NÃO acontece. É aqui que se diz o que sobrevive.
confirmLabelstringVerbo + objeto: “Remover número”. Nunca “OK”.
cancelLabelstring"Cancelar"Raramente muda.
onConfirm() => voidO diálogo fecha sozinho depois.
destructivebooleanfalsePinta o botão de confirmar de vermelho. Só quando algo some.

A tarja de confirmação

Dentro de uma lista, o diálogo é pesado demais e o duplo clique é perigoso. A tarja abre ABAIXO da linha, diz o que some e leva o botão de confirmar pra dentro dela.
Luva de procedimentoem 5 fichasR$ 0,24/un
clique em Excluir: o gatilho SAI DE CENA e a decisão inteira passa a morar na tarja

O gesto muda de lugar

Dois cliques no MESMO ponto não são confirmação: o dedo repete o gesto sem ler. Aqui o segundo clique é noutro lugar, com outro rótulo.

A consequência fica escrita

sentence diz o que some, em uma frase, no idioma do dono da clínica: “Sai das 5 fichas que usam este insumo e o custo delas cai.”

O foco abre no Cancelar

A saída segura é o padrão, e quem usa leitor de tela ouve a consequência (aria-describedby) antes de decidir.

Esc cancela

Como em qualquer diálogo — e trava enquanto pending: cancelar o que já foi enviado não desfaz nada.

A resposta desarma

useConfirm(state) escuta o retorno da action: uma exclusão RECUSADA fechava a pergunta antes, e cada clique novo repetia a recusa sem mudar nada na tela.

Âmbar pra quem apaga, verde pra quem só grava

tone="default" existe pro gesto que não destrói (aplicar preço): a mesma tarja em verde suave, sem o vermelho — o âmbar ali mentia o peso.

o trio
const confirmar = useConfirm(state);   // state = retorno da action

<ConfirmTrigger label="Excluir" armed={confirmar.armed} onArm={confirmar.arm}
                returnFocus={confirmar.returnFocus} ariaLabel={`Excluir ${nome}`} />

{confirmar.armed && (
  <ConfirmStrip
    sentence="O que some, em uma frase."
    confirmLabel="Excluir insumo" busyLabel="Excluindo…"
    pending={pending} action={excluirAction} fields={{ id }}
    onCancel={confirmar.disarm}
  />
)}

Armadilha já pagaO bug que matou o duplo clique

Um botão que TROCA pra type="submit" dentro do próprio onClick submete NAQUELE clique: o React libera o setState de evento discreto antes de o navegador executar a ação padrão. A confirmação de dois cliques existia no código e nunca na tela — toda ação destrutiva de linha excluía no primeiro clique. Botão fica type="button" sempre, e o envio é explícito.

Qual dos três

A escolha é pelo peso da interrupção, não pelo tamanho da ação.
SituaçãoPeçaPor quê
Criar/editar algo curto a partir de um botão de telaDialogA tarefa precisa de foco, e a tela por trás continua sendo o contexto.
Ação destrutiva a partir de um botão de tela ou de menuConfirmDialogInterromper é o ponto: a pessoa precisa parar antes de destruir.
Ação destrutiva numa LINHA de listaConfirmStripModal por linha é pesado, e o duplo clique não confirma nada.
Ação que só grava (aplicar preço sugerido)ConfirmStrip tone="default"Nada some — o vermelho e o âmbar mentiriam o peso.
Formulário longoTela ou gaveta, nunca modalModal com rolagem interna esconde metade do que se está preenchendo.

Sim

Excluir o cadastro de Marina Alvez?

O histórico de atendimento permanece, mas ela sai das listas ativas. Essa ação não tem volta.

Diz o que some, o que fica, e que não tem volta. Nomeia o objeto.

Não

Você tem certeza?

Esta ação não pode ser desfeita.

Certeza de quê? Sem o objeto e sem a consequência, a pergunta só treina o dedo a clicar em “Sim”.

O padrão de foco em camadas não entra em diálogo nem em gaveta: os dois já vêm com véu próprio, e empilhar os dois dá dois escurecimentos brigando.