KDS
Overlays

Dialog

Modal centrado para decisões focadas e fluxos curtos — com modelo de 3 tiers para confirmações.

Overview

Dialog é o overlay centrado e bloqueante do KDS — montado sobre um overlay de 50% de opacidade, recebe foco automaticamente e prende o foco até o usuário fechar. Use para decisões focadas ou fluxos curtos que precisam interromper o contexto: confirmar uma ação, editar um item, mostrar um detalhe específico.

A escolha entre Dialog, Sheet, Popover e Drawer é uma escolha de peso da interrupção:

Quando o conteúdo…Use
precisa de foco total e é curto (1–3 campos, 1 decisão)Dialog
é um fluxo longo / múltiplas seções (filtros, settings)Sheet
é inline e contextual (date picker, mini form)Popover
é mobile-first com gesto de arrastarDrawer
Preview
Dialog padrão com header, conteúdo e footer.

Anatomy

<Dialog>
  <DialogTrigger />              ← visível, no fluxo
  <DialogContent>                ← portalado, centralizado
    <DialogHeader>
      <DialogTitle />            ← pergunta-pergunta, frase clara
      <DialogDescription />      ← contexto breve
    </DialogHeader>
    {/* corpo do dialog */}
    <DialogFooter>
      <DialogClose>Cancelar</DialogClose>   ← saída segura à esquerda
      <Button>Confirmar</Button>            ← ação primária à direita
    </DialogFooter>
  </DialogContent>
</Dialog>

A anatomia é estrita: título como pergunta direta, descrição opcional para contexto, footer com saída à esquerda + ação à direita (convenção ocidental). O DialogContent já cuida de overlay, focus trap, animação e o X no canto superior — passe showCloseButton={false} quando o X for redundante (poucos casos).

Subcomponents

ComponenteDescrição
DialogRaiz que controla open/closed.
DialogTriggerElemento clicável que abre o dialog.
DialogContentContainer portalado com overlay e focus trap.
DialogHeaderWrapper de título + descrição.
DialogTitleTítulo obrigatório (semântica).
DialogDescriptionDescrição opcional anunciada por SR.
DialogFooterWrapper das ações no rodapé.
DialogCloseWrapper que fecha o dialog ao clicar.
DialogPortal / DialogOverlayExpostos para casos avançados.

Usage

import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
  DialogClose
} from "@kalvner/kds/overlays/dialog";
import { Button } from "@kalvner/kds/forms/button";

export function ConfirmPublish() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Publicar</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Publicar este post?</DialogTitle>
          <DialogDescription>
            Ele ficará visível para todos os assinantes.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose asChild>
            <Button variant="ghost">Cancelar</Button>
          </DialogClose>
          <Button>Publicar</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Stakes

Confirmações não são todas iguais. KDS usa um modelo de 3 tiers para escolher quanto atrito impor antes de uma ação destrutiva. Cada tier tem um padrão visual específico — copie a forma certa para o nível de risco, nem mais, nem menos.

Tier 1 — Low stakes

Ação reversível e de baixo impacto. Excluir um comentário, descartar um rascunho que pode ser reescrito, dispensar uma notificação. Use um Dialog comum com botão default (não destructive) — o objetivo é só garantir que não foi clique acidental.

Tier 1 · Excluir comentário
Confirmação básica. Botão padrão. Sem ênfase de risco.

Tier 2 — Medium stakes

Destrutivo mas recuperável. Arquivar um projeto, expulsar um membro, mover algo para a lixeira. O Dialog ganha um botão destructive (vermelho), e a descrição lista o que vai acontecer. Combine sempre com um undo via Sonner toast logo após a operação — Linear faz isso para movimentação de issues, Gmail para arquivamento. O par dialog → toast com Desfazer é o que torna o gesto seguro.

Tier 2 · Arquivar projeto
Destructive button + descrição explícita + undo via Sonner.

Tier 3 — High stakes

Destrutivo e irreversível. Excluir conta, deletar repositório, limpar histórico. O usuário precisa digitar o nome do recurso (ou uma palavra-chave como DELETE) para habilitar o botão. Padrão canônico — GitHub para deletar repos, Stripe para fechar contas, Linear para projetos. O objetivo não é só evitar clique acidental; é forçar atenção plena a um gesto que não tem volta.

Tier 3 · Excluir conta
Typed confirmation. Digite DELETE para habilitar.

Decision matrix

ReversibilidadeImpacto baixoImpacto alto
ReversívelTier 1 — Dialog comumTier 2 — Dialog destructive + undo
IrreversívelTier 2 — Dialog destructiveTier 3 — Typed confirmation

Quando ficar em dúvida, suba um tier. O custo de fricção extra é sempre menor do que o custo de uma exclusão acidental — sobretudo em contextos profissionais onde não há "ctrl-Z global". Para confirmações de Tier 2 e Tier 3, considere usar AlertDialog em vez de DialogAlertDialog é não-cancelável por overlay click, forçando uma escolha explícita.

When to use

  • Confirmar uma ação que pode causar surpresa ou perda.
  • Apresentar um formulário curto (1–3 campos) que precisa de foco.
  • Mostrar detalhes de um item em modo focado, sem trocar de página.
  • Onboarding com 1–2 passos rápidos.

When not to use

  • Para fluxos longos ou multi-seção, use Sheet.
  • Para conteúdo contextual e inline, use Popover.
  • Para feedback transiente após uma ação concluída, use Sonner (toast).
  • Para alertas críticos que não permitem cancelar por overlay click, use AlertDialog.
  • Nunca aninhe Dialogs. Se o segundo passo precisa de outro contexto pesado, repense o fluxo (wizard inline, navegação, Sheet).

Best practices

  • Título como pergunta direta. "Publicar este post?" lê melhor do que "Confirme a publicação". Verbo na frente, recurso explícito.
  • Descrição é contexto, não muleta. Se o título não disser claramente o que acontece, conserte o título primeiro.
  • Ordem ocidental no footer. Cancelar à esquerda, ação à direita. O olho percorre da esquerda para a direita até o commit.
  • Overlay click = cancelar. O comportamento padrão fecha o dialog sem confirmar. Não desabilite isso em Tier 1.
  • Foco automático. Não force foco em campos perigosos. Um campo vazio com auto-focus no botão destructive é uma armadilha.
  • Anuncie mudanças. Se o conteúdo do dialog muda dinamicamente (ex.: campo de typed confirmation), use aria-live para que SRs acompanhem.
  • Não aninhe. Dois dialogs sobrepostos confundem foco e leitura por screen reader. Em vez disso, use steps inline ou navegação.

Accessibility

ConcernComportamento
RolesRadix Dialog aplica role="dialog" + aria-modal="true".
FocoFoco entra automaticamente no primeiro elemento focável; preso até o close (focus trap). Volta para o trigger ao fechar.
KeyboardEsc fecha. Tab/Shift+Tab cicla entre os focáveis dentro do dialog.
Screen readerDialogTitle é obrigatório — anunciado como nome do dialog. DialogDescription é anunciado quando vinculado via aria-describedby (Radix faz isso automaticamente).
Layout shiftComo o dialog é portalado e o body recebe overflow: hidden, há uma gutter de scrollbar — Radix compensa com padding-right para evitar CLS. Ver [[ui/layout/cumulative-layout-shift]].
Touch targetBotões no footer respeitam o tamanho mínimo do Button primitive (36px / lg = 40px).
Reduced motionA animação enter/exit usa data-[state=open/closed]:animate-in/out; respeite prefers-reduced-motion no nível do tema.
  • AlertDialog — variante modal+blocking, usada para Tier 2 e Tier 3 quando o usuário precisa decidir.
  • Sheet — para fluxos longos ou painéis laterais.
  • Drawer — bottom sheet para mobile.
  • Sonner — toasts transientes; o canal natural para fechar o loop "Excluído. Desfazer."
  • Button — variantes default e destructive usadas no footer.

On this page