Olha só isso: você provavelmente está usando uma lib de modal sem precisar

Fala, dev! 😄 Já parou pra pensar que o <dialog> está aí há quase dez anos e a gente continua importando 40kb de biblioteca pra fazer um modalzinho? Pois é. O elemento nativo entrega focus trap, inert no fundo, fechamento com Esc e empilhamento no top-layer — tudo de graça.

Neste tutorial passo a passo, a gente vai destrinchar cada detalhe: abrir, fechar, estilizar, animar e — o mais importante — escapar das armadilhas de acessibilidade que quebram em produção.

Quer ver esse mesmo princípio de "use a primitiva da plataforma antes da lib" aplicado em segurança de infraestrutura? Dá uma olhada nesse modelo de defesa em três camadas no AWS SageMaker.

Developer inspecting native HTML dialog element in browser DevTools System Abstract Visual

A API básica: show() vs showModal()

Começando pelo markup:

<button id="dialog-button">Abrir Dialog</button>
<dialog id="dialog">
  <p>E aí, dev! 👋</p>
  <button id="dialog-close">Fechar</button>
</dialog>

Agora o JS:

const dialogButton = document.querySelector('#dialog-button');
const dialog = document.querySelector('#dialog');
const closeBtn = document.querySelector('#dialog-close');

// Não-modal: sem backdrop, sem centralização, sem Esc
dialogButton.addEventListener('click', () => dialog.show());

// Modal: backdrop + centralização + Esc + focus trap + fundo inert
dialogButton.addEventListener('click', () => dialog.showModal());

closeBtn.addEventListener('click', () => dialog.close());

Fechamento declarativo (sem JS!)

<form method="dialog">
  <button>Fechar</button>
</form>

Qualquer <button> dentro de um <form method="dialog"> fecha o dialog sem uma linha de JavaScript. Sim, é mágico. ✨

Invoker Commands (experimental)

<button commandfor="my-dialog" command="show-modal">Abrir</button>
<dialog id="my-dialog">...</dialog>
<button commandfor="my-dialog" command="close">Fechar</button>

E dá pra escutar o evento command:

document.querySelectorAll('dialog').forEach(dialog => {
  dialog.addEventListener('command', (event) => {
    if (event.command === 'show-modal') {
      console.log('Dialog aberto como modal');
    }
  });
});

Gostou da ideia de usar a plataforma nativa? Então você vai curtir esse artigo sobre mundos generativos em tempo real rodando em GPU de consumidor.

CSS backdrop and modal dialog styling example in code editor Algorithm Concept Visual

Estilizando o Dialog e o Backdrop

O pseudo-elemento ::backdrop

dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(4px);
}

Selecione o estado [open], não o elemento base

/* 👎 Não aparece — dialog fica display:none quando fechado */
dialog {
  background: gold;
  border-radius: 12px;
}

/* 👍 Correto */
dialog[open] {
  background: gold;
  border-radius: 12px;
  border: 0;
}

Bloquear o scroll do fundo

body:has(dialog[open]) {
  overflow: hidden;
}

Ou, no Chrome 144+, a rota mais declarativa:

dialog {
  overflow: hidden;
  overscroll-behavior: contain;
}
dialog::backdrop {
  overscroll-behavior: contain;
}

Animando a entrada com @starting-style

@starting-style {
  dialog[open] {
    opacity: 0;
  }
}

dialog {
  transition: opacity .3s ease-in-out;
}
dialog[open] {
  opacity: 1;
}

⚠️ Limitações e pegadinhas

  • show() não é showModal(). Só o showModal() faz focus trap e deixa o fundo inert. show() vira quase um popover.
  • Suporte do :open no Safari chegou recente — prefira [open] pra ter mais alcance.
  • Não existe closeModal(). close() resolve os dois casos.
  • Scrollbar sumindo muda o layout. Adicione scrollbar-gutter: stable no [open].
  • Leitor de tela precisa de label real. Um "X" sozinho não é label — use <span class="sr-only">Fechar</span> + aria-hidden="true" no SVG.
  • View Transitions não combinam bem com dialog por causa do top-layer. Use CSS animation pra saída.

📚 Próximos passos

  • Compare com a Popover API — Popover pra tooltips/menus, Dialog pra modais.
  • Leia o breakdown do Zell Liew sobre acessibilidade: Popover não tem focus management nem ARIA por padrão.
  • Fique de olho nas invoker commands quando estabilizarem.

Animated modal dialog with fade transition on a web page Software Concept Art

Fechando a conta

O <dialog> nativo é um caso raro onde a primitiva da plataforma vence a maioria das libs — bundle menor, a11y melhor por padrão e zero bugs de focus trap pra debugar. Use sempre que precisar de modal, e só importe lib quando precisar de posicionamento avançado ou animações cross-browser muito específicas.

TL;DR:

  • showModal() pra modais, show() pra popovers.
  • Estilize via dialog[open] e ::backdrop.
  • Anime com @starting-style.
  • Bloqueie scroll com body:has(dialog[open]).

Leitura relacionada:

Fonte: CSS-Tricks — Using and Styling the Dialog Element

Este conteúdo foi elaborado com o auxílio de ferramentas de IA, com base em fontes confiáveis, e revisado pela nossa equipe editorial antes da publicação. Não substitui o aconselhamento de um profissional especializado.