Início / Componentes / Modais

Modais

Modal interrompe. É uma ferramenta pesada que tira o usuário do fluxo principal e exige uma ação antes de continuar. Use quando a tarefa precisa de foco total e não tem alternativa mais leve.

Quando usar modal

Modal faz sentido quando a ação é:

  • Destrutiva e irreversível: "Tem certeza que quer deletar?" precisa de confirmação explícita
  • Contextual e breve: criar ou editar algo sem perder o contexto da página atual
  • Necessita de foco total: a ação não pode ser feita em background

Para os outros casos, existem alternativas mais leves. Drawer para configurações secundárias. Edição inline para campos simples. Nova página para formulários longos ou fluxos multi-step.

Acessibilidade: focus trap e announce

Modal tem requisitos de acessibilidade específicos que não são opcionais:

  • Focus trap: o foco do teclado não pode sair do modal enquanto está aberto. Tente navegar com Tab no exemplo abaixo — o foco fica dentro.
  • Retorno de foco: quando o modal fecha, o foco volta para o elemento que o abriu
  • Escape fecha: padrão universal que usuários de teclado esperam
  • Role e labels: role="dialog", aria-labelledby apontando para o título, aria-modal="true"

Comportamento esperado

Ao abrir: foco vai para o primeiro elemento interativo dentro do modal — ou para o próprio container se não houver.

Ao fechar: três formas válidas — botão explícito de fechar, tecla Escape, ou clique no overlay. Cuidado com o clique no overlay em formulários: o usuário pode perder dados preenchidos sem querer.

A decisão

O modal usa HTML, CSS e JavaScript puro. Focus trap, Escape, e atributos ARIA são implementados manualmente — é o preço de não usar uma biblioteca, mas garante controle total sobre o comportamento.

/* HTML */
<dialog id="confirm-delete">
  <form method="dialog">
    <h2>Confirmar exclusão</h2>
    <p>Esta ação não pode ser desfeita.</p>
    <button type="button" onclick="close()">Cancelar</button>
    <button type="submit">Deletar</button>
  </form>
</dialog>
/* CSS */
dialog {
  border: none;
  border-radius: 8px;
  padding: 1.5rem;
  background: var(--background);
  border: 1px solid var(--border);
}

dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
}
/* JavaScript */
const dialog = document.getElementById('confirm-delete');

dialog.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    e.preventDefault();
    dialog.close();
  }
});

O elemento <dialog> nativo do HTML já tem muito do comportamento esperado: fecha com Escape, suporta ::backdrop, e o método showModal() cria o focus trap automaticamente.