Visão geral
Use o elemento nativo <dialog> para caixas de diálogo modais e não modais. Ele fornece a semântica de acessibilidade, o processamento de foco, a renderização da camada superior e o processamento de Esc que as sobreposições personalizadas precisam recriar manualmente.
Escolha o tipo de sobreposição
Escolha a primeira regra correspondente. Se não tiver certeza, use a Regra 1.
Regra 1: a sobreposição interrompe o usuário e bloqueia a página (padrão)
Use para confirmações, formulários, login, ações destrutivas e qualquer solicitação "modal", "caixa de diálogo", "lightbox" ou "pop-up" que não diga o contrário.
Use <dialog> aberto como um modal, de forma declarativa com command="show-modal" (consulte Controle declarativo de caixas de diálogo e pop-overs) ou de um script com showModal().
<button type="button" command="show-modal" commandfor="confirm">Delete</button>
<!-- MANDATORY: no role="dialog" or aria-modal; the native modal already provides them -->
<dialog id="confirm" aria-labelledby="confirm-title">
<h2 id="confirm-title">Delete this item?</h2>
<form method="dialog">
<button value="cancel" autofocus>Cancel</button>
<button value="delete">Delete</button>
</form>
</dialog>
O navegador oferece: página inerte, contenção de foco, camada superior, ::backdrop, Esc e dispensa por gesto de voltar, além de retorno do foco ao invocador.
- NÃO adicione
role="dialog",aria-modal, uma armadilha de foco, alternância deinertou um gerenciador deEsc. - NÃO abra com
show()ou o atributoopen. - FAÇA a rolagem da página parar atrás dele com o CSS abaixo. Consulte Impedir que a página abaixo de uma caixa de diálogo modal role para mais detalhes.
- NÃO bloqueie a rolagem com
overflow: hiddenouposition: fixedemhtmloubody.
/* MANDATORY (both rules): without them, scrolling over the backdrop or inside the dialog scrolls the page behind it */
dialog:modal {
overscroll-behavior: contain; /* stops scroll gestures in the dialog chaining to the page */
}
dialog::backdrop {
overflow: hidden; /* ::backdrop is not a scroll container by default, so it would ignore overscroll-behavior */
overscroll-behavior: contain;
}
Não use em menus ou dicas: consulte a Regra 3.
Regra 2: o usuário continua trabalhando na página enquanto o painel está aberto
Use para barras de pesquisa, inspetores, paletas e painéis de composição que ainda são caixas de diálogo semanticamente.
Use <dialog popover> para um painel de camada superior com dispensação de popover (auto dispensa leve, manual não). Use dialog.show() somente quando o painel não puder estar na camada superior.
<button type="button" popovertarget="find">Find</button>
<dialog id="find" popover="manual" aria-labelledby="find-title">
<h2 id="find-title">Find</h2>
<input type="search" aria-label="Search">
<button type="button" popovertarget="find" popovertargetaction="hide">Close</button>
</dialog>
A página permanece interativa e não fica inerte.
- É RECOMENDÁVEL incluir um botão de fechar visível.
- NÃO use
showModal(), que bloqueia a página.
Não para bloquear confirmações: consulte a Regra 1.
Regra 3: a sobreposição não é uma caixa de diálogo
Use para menus, menus suspensos, dicas, cards de passagem do cursor e notificações.
Use um elemento semântico com o atributo popover, não <dialog>.
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
Escolha como ele pode ser dispensado
O padrão depende de como a caixa de diálogo é aberta. Se closedby estiver ausente ou for inválido, showModal() vai se comportar como closerequest, e qualquer outra forma de abertura vai se comportar como none.
- Somente gesto de voltar e Esc (padrão modal): não defina nada ou
closedby="closerequest". - Também clique fora para fechar: use
closedby="any". - Somente o desenvolvedor fecha a página (por exemplo, um formulário com mudanças não salvas): use
closedby="none"e adicione um caminho de confirmação. Consulte "Encerramento" abaixo.
Diretrizes
Nome acessível e foco inicial
- É RECOMENDÁVEL nomear cada caixa de diálogo com
aria-labelledbyapontando para o cabeçalho ouaria-labelquando não houver um título visível. - RECOMENDADO: coloque
autofocusno controle principal ou no primeiro campo. - RECOMENDADO: coloque
autofocusna ação segura (como "Cancelar" ou "Manter") em uma confirmação destrutiva. - NÃO escreva código para retornar o foco ao fechar. O navegador o retorna ao invocador.
Abertura
- RECOMENDAÇÃO: abra de forma declarativa com
command="show-modal"ecommandforem um<button>. Consulte Controle declarativo de caixas de diálogo e pop-overs. - USE
dialog.showModal()ao abrir pelo script.
Encerramento
- RECOMENDADO: feche com os botões
<form method="dialog">oucommand="close". - FAÇA a escolha de
closedbyde forma deliberada:any(dispensa leve eEsc),closerequest(somenteEsc, o padrão modal) ounone(somente fechamento controlado pelo desenvolvedor). - USE
command="request-close"ourequestClose()para botões de cancelamento que precisam respeitar a lógica de mudanças não salvas. Eles acionamcancelprimeiro e ignoramclosedby. - TRATE o
closedbycomo um aprimoramento progressivo.Esce o gesto de voltar em dispositivos móveis já fecham os modais sem ele.
Conexão com o fechamento
- FAÇA a leitura de
dialog.returnValueem um listenerclosepara saber qual botão foi usado e, em seguida, redefina-o comdialog.returnValue = ''.Esce outras solicitações de fechamento não definem nenhum valor, e os navegadores diferem quanto à limpeza do valor anterior. Portanto, sem a redefinição, um valor desatualizado pode vazar. Trate''como "dispensado sem escolha". - USE
cancelpara interceptarEscerequest-close, mas não usepreventDefault()para pressionamentos repetidos deEscsem ativação do usuário. - RECOMENDAMOS usar
closedby="none"com uma etapa de confirmação explícita para formulários sujos. - RECOMENDADO: informe ao usuário por que a caixa de diálogo permaneceu aberta quando
cancelé impedido. Mostre uma mensagem visível e faça referência a ela comaria-describedbyno controle que recebe o foco (como "Descartar") para que um leitor de tela a leia quando o foco for movido. Sem ele, o foco que se move para um novo botão anuncia apenas o nome desse botão. - NÃO deixe que a confirmação mova os controles para baixo do ponteiro. Reserve o espaço da mensagem (oculte-a com
visibility, não comdisplayouhidden) e adicione o botão destrutivo onde os botões atuais não se movem (por exemplo, primeiro em uma linha alinhada à direita) para que um segundo clique no mesmo lugar não descarte as mudanças do usuário.
Caixas de diálogo não modais
- USE
<dialog popover>(autooumanual) para um painel de camada superior edialog.show()quando não quiser a camada superior. - RECOMENDAMOS incluir um botão "Fechar" visível em todas as caixas de diálogo não modais.
Plano de fundo e estilo
- ESTILIZE
::backdrope usedialog:modalpara segmentar estilos somente modais. - RECOMENDAMOS deixar a animação para Animar elementos de e para a camada superior, arrastar para Criar um elemento arrastável e redefinir o elemento pai para Interface persistente da camada superior.
- NÃO defina
displayemdialogsem condições. Ele substitui o estado fechado. Defina o escopo comdialog[open].
Acessibilidade
- NÃO adicione
role="dialog",aria-modal, uma armadilha de foco manual ou alternância deinerta um modal nativo. - USE um
<button>nativo com um nome acessível para controles de fechamento. - RECOMENDADO: use um nível de título consistente para os títulos de caixas de diálogo.
Estratégias de fallback
closedby (dialog-closedby)
Sem closedby, um modal ainda é fechado com Esc e o gesto de voltar. Somente o clique fora é perdido, então essa é uma degradação suave. closedby="none" é ignorado da mesma forma: Esc aciona cancel e fecha a caixa de diálogo. Mantenha a proteção de mudanças não salvas em um gerenciador cancel, não apenas em closedby="none", para que ela também proteja onde closedby está ausente. Se o destino de referência exigir uma dispensa leve, adicione o seguinte onde closedby estiver faltando:
if (!('closedBy' in HTMLDialogElement.prototype)) {
// Test the pointer position, not event.target: the <dialog> is also the target for its own
// padding and the gaps between its children, so a target check would close on those clicks
const outside = (e, dialog) => {
const r = dialog.getBoundingClientRect();
return e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
};
for (const dialog of document.querySelectorAll('dialog[closedby="any"]')) {
let pressedOutside = false;
dialog.addEventListener('pointerdown', (e) => { pressedOutside = outside(e, dialog); });
// Require the press and the release outside, so a text-selection drag that ends on the backdrop does not dismiss
dialog.addEventListener('pointerup', (e) => { if (pressedOutside && outside(e, dialog)) dialog.close(); });
}
}
overscroll-behavior
Trate a contenção de rolagem como um aprimoramento progressivo. Não use a detecção de recursos: @supports informa que a propriedade é compatível, mesmo quando contêineres não roláveis a ignoram. Quando não é aplicada, a caixa de diálogo ainda contém rolagem sempre que o próprio conteúdo transborda, e showModal() ainda torna a página inerte. Não use overflow: hidden ou position: fixed em html ou body, porque isso remove a barra de rolagem raiz e muda o layout.
Comandos do invocador (command / commandfor)
Se o destino do Baseline não incluir comandos do invocador, mantenha a mesma marcação e carregue o polyfill de forma condicional (a mesma experiência, como aprimoramento progressivo). Detecte na interface e nunca carregue de forma incondicional:
// MANDATORY: feature detect on HTMLButtonElement.prototype and load only when native support is missing
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('invokers-polyfill'); // npm install invokers-polyfill
}
Somente as regras 2 e 3 usam o Popover.
Alternativas e suporte do navegador para Popover
A API Popover é, em grande parte, um aprimoramento progressivo, mas os comportamentos que a definem (promoção de camada superior, fechamento leve e invocação de popovertarget) não têm equivalente somente em CSS. Os navegadores mais antigos precisam de um polyfill ou de um substituto manual, se você preferir não enviar um.
Polyfill: para oferecer suporte ao atributo popover em navegadores mais antigos, carregue condicionalmente @oddbird/popover-polyfill. OBRIGATÓRIO: detecte o recurso verificando a propriedade popover em HTMLElement.prototype e carregue o polyfill somente quando não houver suporte nativo. NÃO o carregue incondicionalmente.
Com um bundler ou mapa de importação:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Sem um bundler, importe de uma CDN dentro de um <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Observação sobre o estilo: o polyfill não pode definir a pseudoclasse :popover-open real. Por isso, ele aplica uma classe .\:popover-open. OBRIGATÓRIO: combine os dois com :is() ou :where(). Caso contrário, os navegadores que não têm :popover-open vão descartar toda a regra:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Como alternativa, para um substituto legado sem um polyfill, use position: fixed e calcule manualmente as coordenadas usando getBoundingClientRect() ou use o posicionamento padrão com inset: auto, se isso for aceitável para o caso de uso.