Um modal pode parecer correto e ainda desmontar a navegação da página. O fundo escurece, a caixa aparece no centro e o botão de fechar está visível. Enquanto isso, o foco continua no CTA que ficou atrás, Tab percorre links ocultos e Esc não faz nada. Para quem usa teclado ou leitor de tela, não existe um diálogo: existe uma página que mudou sem explicar como operar o novo estado.

Adicionar role="dialog" não corrige o comportamento sozinho. A interface precisa mover o foco para o conteúdo aberto, manter a interação dentro dele, oferecer um fechamento previsível e devolver a pessoa ao fluxo anterior. A revisão a seguir cobre esse contrato, a implementação com HTML nativo e o teste da URL publicada.

Confirme se o conteúdo precisa ser modal

Um diálogo modal interrompe o restante da página. Enquanto ele está aberto, o conteúdo externo fica inerte. Essa interrupção pode fazer sentido para confirmar uma ação, concluir uma etapa curta ou coletar uma informação necessária antes de continuar.

Não use modal apenas para destacar conteúdo. Antes de construir um, pergunte:

  • a pessoa precisa concluir ou descartar esta etapa antes de voltar à página?
  • o conteúdo cabe numa interação curta e delimitada?
  • abrir outra rota ou expandir uma seção manteria melhor o contexto?
  • a informação precisa interromper a tarefa atual?
  • o mesmo resultado funciona sem JavaScript?

Um formulário longo, uma política extensa ou uma comparação detalhada costuma funcionar melhor numa página própria. Quanto maior o conteúdo, mais difícil escolher foco inicial, manter contexto e lidar com telas pequenas.

O padrão de diálogo modal da W3C define a base: o foco entra no diálogo, Tab e Shift+Tab permanecem nele, Esc fecha e o foco normalmente retorna ao elemento que abriu a janela.

Prefira o elemento dialog quando ele atende ao caso

O elemento HTML dialog oferece comportamento nativo para uma janela sobreposta. Aberto com showModal(), ele entra na camada superior do documento e torna o restante da página inerte.

<button type="button" id="abrir-contato">Pedir contato</button>
<dialog id="contato" aria-labelledby="contato-titulo">
<h2 id="contato-titulo">Como você prefere conversar?</h2>
<p>Escolha um canal para continuar.</p>
<button type="button" id="fechar-contato">Agora não</button>
</dialog>
const trigger = document.querySelector("#abrir-contato");
const dialog = document.querySelector("#contato");
const closeButton = document.querySelector("#fechar-contato");
trigger.addEventListener("click", () => dialog.showModal());
closeButton.addEventListener("click", () => dialog.close());
dialog.addEventListener("close", () => trigger.focus());

Não troque showModal() por apenas adicionar o atributo open. Um diálogo aberto como não modal não recebe o mesmo isolamento da página. Se o projeto usa um componente pronto, confirme o HTML e o comportamento gerados em vez de confiar no nome da biblioteca.

Quem chega ao modal precisa saber o que abriu. Quando há um título visível, relacione o contêiner com aria-labelledby:

<dialog aria-labelledby="excluir-titulo">
<h2 id="excluir-titulo">Excluir este rascunho?</h2>
...
</dialog>

Use aria-label apenas quando não existe um título visível adequado. Não mantenha duas versões do mesmo texto sem necessidade.

aria-describedby pode associar uma descrição curta e simples. Evite usá-lo para apontar para vários parágrafos, listas ou tabelas: a tecnologia assistiva pode anunciar tudo como uma sequência longa na abertura. Nesses casos, dê foco inicial a um título ou bloco estático e permita que a pessoa explore a estrutura.

O guia sobre nomes acessíveis de links e botões ajuda a revisar também o gatilho, o botão de fechar e as ações internas.

Escolha o foco inicial pela tarefa

“Focar o primeiro botão” não é uma regra universal. A posição mais útil depende do conteúdo:

  • num formulário curto, o primeiro campo pode ser o destino natural;
  • numa mensagem longa, foque o título com tabindex="-1" para preservar o começo do conteúdo;
  • numa confirmação destrutiva, considere iniciar na ação menos destrutiva;
  • numa etapa informativa curta, a ação principal pode ser adequada;
  • se o primeiro controle fica abaixo da dobra, foque um elemento estático no topo.

O foco não pode permanecer no fundo. Também não deve cair no body, num contêiner sem nome ou num botão escolhido apenas porque aparece primeiro no DOM.

Ao fechar, devolva o foco ao controle que abriu o modal. Se esse controle deixou de existir, escolha o próximo ponto lógico do fluxo. Por exemplo, depois de criar um item, o foco pode ir para o novo item em vez de voltar ao botão “Adicionar”.

Mantenha teclado e ponteiro no mesmo estado

Enquanto o modal está ativo:

  • Tab avança pelos elementos focáveis internos;
  • Shift+Tab volta pelos mesmos elementos;
  • ao sair da última parada, o foco volta à primeira;
  • Esc fecha o diálogo, salvo uma necessidade realmente justificada;
  • o conteúdo de fundo não recebe clique, toque nem foco;
  • existe um botão visível de fechar ou cancelar.

Não use tabindex positivo para montar uma ordem artificial. Organize o DOM na sequência da tarefa. O roteiro de navegação por teclado cobre ordem, foco visível, armadilhas e elementos encobertos.

Se a implementação não usa dialog, tornar o fundo apenas semitransparente não basta. É preciso impedir interação externa e garantir que a árvore de acessibilidade represente o mesmo estado. O atributo aria-modal="true" comunica que o diálogo é modal, mas não implementa a inércia nem contém o foco por conta própria.

Feche sem surpreender nem perder dados

Defina todas as saídas possíveis:

  • botão fechar;
  • botão cancelar;
  • tecla Esc;
  • conclusão da ação;
  • clique no backdrop, se o produto adotar esse comportamento;
  • navegação ou desmontagem do componente.

O mesmo processo deve limpar listeners, restaurar rolagem e devolver foco. Se clicar fora fecha a janela, não deixe essa ser a única saída. Em formulários com dados digitados, avalie se o fechamento acidental precisa de confirmação.

Evite usar um alertdialog para qualquer caixa chamativa. O padrão de alert dialog da W3C é reservado a mensagens importantes que interrompem o fluxo e exigem resposta, como uma confirmação destrutiva ou um erro que precisa ser reconhecido.

Trate conteúdo longo e telas pequenas

Um modal pode caber em desktop e esconder o título ou os botões em 320 pixels CSS. Teste:

  • altura menor do que o conteúdo;
  • teclado virtual aberto;
  • zoom de 200% e 400%;
  • orientação retrato e paisagem;
  • texto ampliado;
  • mensagem de erro que acrescenta linhas;
  • tradução com palavras maiores.

Se houver rolagem, o conteúdo do diálogo deve rolar sem perder o botão de saída nem mover a página de fundo. Não force uma altura fixa que corta mensagens. O guia de zoom e reflow mostra como identificar rolagem em duas dimensões e conteúdo encoberto.

Animações de entrada e saída também precisam respeitar a preferência por menos movimento. O artigo sobre movimento reduzido explica como preservar o estado final sem depender de escala, zoom ou deslocamento amplo.

Teste o ciclo completo, não uma captura

Execute o fluxo apenas com teclado:

  1. leve o foco ao gatilho;
  2. abra o modal com Enter ou Espaço;
  3. confirme qual elemento recebeu foco;
  4. percorra todas as paradas com Tab e Shift+Tab;
  5. tente alcançar o conteúdo ao fundo;
  6. feche com Esc;
  7. confirme o retorno do foco;
  8. abra novamente e conclua a ação principal;
  9. repita com erro, conteúdo longo e largura pequena.

Depois use a árvore de acessibilidade e um leitor de tela. Verifique nome, função, descrição, ordem e anúncio dos estados. Um scanner pode encontrar diálogo sem nome ou atributo inválido, mas não prova que o foco voltou ao lugar certo nem que a tarefa continuou compreensível.

Use um pedido de correção verificável

Revise os diálogos modais desta landing page sem publicar ainda.
Escopo:
- fonte e commit: [referência]
- rotas: [lista]
- gatilho e objetivo de cada diálogo: [inventário]
Para cada diálogo:
1. Confirme se a interrupção modal é necessária.
2. Registre elemento, nome acessível, descrição e foco inicial.
3. Teste Tab, Shift+Tab, Escape, clique, toque e fechamento pela ação principal.
4. Impeça interação com o fundo enquanto o modal estiver aberto.
5. Restaure o foco para o gatilho ou próximo ponto lógico.
6. Teste conteúdo longo, erro, zoom e largura estreita.
7. Preserve texto, dados, tracking e lógica de negócio.
Entregue o inventário, o diff e as evidências antes de publicar.

O pedido limita a correção ao comportamento observável. Ele também impede que o agente troque texto, fluxo ou design para mascarar uma falha de foco.

Reteste a versão publicada

Depois da aprovação, siga a documentação de Publicação: publique uma nova Version, acompanhe o deploy e abra a URL final. Repita o ciclo de foco no HTML servido, depois que scripts e integrações carregarem.

Confirme que analytics, formulários, bloqueio de rolagem e botões externos continuam funcionando. Se a build ou a hidratação alterar o diálogo aprovado, corrija a fonte e republique o mesmo Site. O modal só está pronto quando abrir, operar e fechar deixa a pessoa num ponto previsível da página pública.