12k
All articles

Por que seu z-index não está funcionando

Depure por que z-index não funciona usando as ferramentas de desenvolvimento. Encontre o contexto de empilhamento que limita o elemento e corrija modais, menus e cabeçalhos fixos.

OpenReplay Team
OpenReplay Team
Por que seu z-index não está funcionando

Se um elemento com z-index: 9999 continua sendo renderizado atrás de outro com valor 2, algum ancestral do primeiro elemento criou um contexto de empilhamento (stacking context), e um z-index nunca alcança nada fora do contexto ao qual pertence.

Você já tentou 99999. Depois 2147483647. O modal continua embaixo do header, e nada no painel Styles parece errado.

Para a teoria por trás da ordem de empilhamento, consulte CSS: Demystifying the z-index property. Este guia aborda o lado da depuração: como visualizar contextos de empilhamento no DevTools, como subir pelo DOM até encontrar o ancestral que aprisiona seu elemento e como corrigir cada um dos casos mais comuns sem precisar aumentar o número.

Principais pontos

  • Um valor de z-index só compete com elementos do mesmo contexto de empilhamento, então 9999 perde para 2 quando um ancestral do primeiro elemento tem seu próprio contexto de empilhamento.
  • Quando um z-index maior perde, compare os ancestrais dos dois elementos que compartilham um pai em comum. Os valores dos próprios elementos não resolvem a questão.
  • Qualquer transform, filter ou clip-path diferente de none, ou um opacity abaixo de 1, cria um novo contexto de empilhamento mesmo quando o elemento não tem position nem z-index.
  • Um dropdown cortado na borda do seu card geralmente está sendo recortado por overflow, e nenhum valor de z-index vai resolver isso.
  • Mover um overlay para que ele seja filho direto de <body> o tira do contexto de empilhamento de todos os ancestrais.

Qual é a regra única por trás do z-index que não funciona?

Um z-index só ordena um elemento em relação aos outros membros do seu próprio contexto de empilhamento. Um contexto de empilhamento é um grupo que o navegador pinta como uma única unidade. O guia de contexto de empilhamento do MDN lista as propriedades que criam um. Quando um ancestral cria um contexto, o z-index do filho só se aplica dentro desse ancestral. Assim, se o ancestral estiver abaixo de outro contexto, o filho também ficará abaixo dele, seja qual for o número que você atribuir.

Esta é a versão mínima do bug:

<header style="position: fixed; top: 0; z-index: 2; background: tomato;">Header</header>
<main style="opacity: 0.99;">
  <div class="modal" style="position: fixed; inset: 0; z-index: 9999; background: rgb(0 0 0 / 0.6);">
    Modal
  </div>
</main>

O modal perde porque main tem opacity: 0.99, o que transforma main em um contexto de empilhamento. Dentro de main, o 9999 do modal funciona perfeitamente. Mas o próprio main é comparado com o header no nível raiz, e o 2 do header é maior. Quando um z-index maior perde, olhe para os ancestrais: encontre o ancestral mais próximo de cada elemento que compartilha um pai em comum com o outro e compare esses dois.

Como visualizar contextos de empilhamento no DevTools?

O DevTools dos navegadores baseados em Chromium oferece ferramentas visuais para investigar problemas de sobreposição de camadas, o que pode ser mais rápido do que tentar adivinhar. Tanto o Chrome quanto o Edge abrem essas visualizações pelo Command Menu, usando Command+Shift+P no macOS ou Control+Shift+P no Windows e no Linux. Para conhecer mais painéis do DevTools que costumam passar despercebidos, veja Hidden Gems in Chrome DevTools.

Chrome: o painel Layers

  1. Abra o DevTools e pressione Command+Shift+P ou Control+Shift+P.
  2. Digite Layers e escolha Show Layers panel. Você também pode acessar More options > More tools > Layers panel.
  3. Gire o canvas e selecione uma camada para ver seus detalhes, incluindo o motivo pelo qual o navegador lhe atribuiu uma camada própria.

O painel Layers exibe uma árvore com as camadas da página à esquerda e um diagrama 3D delas ao lado. Use o painel para inspecionar como o navegador separou a página em camadas de composição (compositing layers), mas lembre-se de que elas não são a mesma coisa que contextos de empilhamento. Para encontrar o ancestral responsável por um contexto de empilhamento, use o método de subida pelos ancestrais descrito abaixo. O painel também aparece em debugging janky CSS animations.

Edge: 3D View

  1. Abra o Command Menu e execute Show 3D View.
  2. Selecione a aba Z-index.
  3. Marque a opção Show all para ver os elementos posicionados junto com os contextos de empilhamento.

O 3D View do Edge tem três abas: Composited Layers, Z-index e DOM. A aba Z-index desenha os contextos de empilhamento da página em 3D, de modo que um elemento aprisionado aparece aninhado dentro de uma placa mais baixa.

Se você quiser uma visualização em árvore dos contextos de empilhamento no Chrome ou no Firefox, a extensão CSS Stacking Context inspector adiciona um painel com a árvore de contextos, além de uma barra lateral no painel Elements que explica por que o elemento selecionado cria um contexto. A documentação da extensão informa que ela ainda não consegue inspecionar o interior de iframes de terceiros.

O método de subida pelos ancestrais

O método de subida pelos ancestrais encontra o ancestral que aprisiona o elemento usando apenas o painel Elements e o painel Computed. Ele funciona da mesma forma em qualquer navegador.

  1. Selecione o elemento que está perdendo (o modal, o menu).
  2. No painel Elements, clique no pai dele.
  3. Abra o painel Computed e digite cada propriedade do checklist abaixo na caixa de filtro: transform, opacity, filter, position, z-index e assim por diante.
  4. Se nenhuma delas criar um contexto, suba um nível e repita até chegar ao <html>.
  5. Anote o primeiro ancestral que cria um contexto.
  6. Repita os passos de 1 a 5 partindo do elemento que está vencendo.
  7. As duas cadeias se encontram em um ancestral em comum. Os dois filhos dele que estão nessas cadeias são os elementos que de fato são comparados.

Se você estiver trabalhando a partir de um relato de bug, e não de uma reprodução local, o session replay mostra o sintoma do ponto de vista do usuário, como um clique que cai em um overlay invisível ou um menu que abre atrás do conteúdo. Isso indica qual elemento selecionar no passo 1.

Checklist: propriedades que criam um contexto de empilhamento

Verifique cada ancestral com base nesta tabela. Basta uma linha correspondente para aprisionar seus descendentes.

PropriedadeCria um contexto quandoO que procurar no Computed
position: absolute / relativez-index não é autoOs dois valores definidos no mesmo elemento
position: fixed / stickySempreApenas o valor de position
opacityAbaixo de 1Valores como 0.99, muitas vezes resquícios de uma animação de fade
transform, scale, rotate, translateQualquer valor diferente de noneUm valor matrix(...), incluindo transformações identidade
filter, backdrop-filterDiferente de noneblur(), drop-shadow()
clip-path, perspectiveDiferente de noneQualquer forma ou comprimento
mask, mask-image, mask-borderDiferente de noneValores de imagem ou gradiente
will-changeLista uma propriedade que criaria um contexto se fosse definidawill-change: transform ou opacity
isolationisolateValor explícito
containlayout, paint, strict ou contentComum em listas virtualizadas
Filho de flex ou gridz-index não é autodisplay: flex ou grid no pai. Não é necessário position

A lista completa do MDN também inclui mix-blend-mode diferente de normal, elementos da top layer e elementos cujas propriedades que criam contexto são animadas com animation-fill-mode: forwards. O MDN ainda lista container-type: size e inline-size, mas o CSS Working Group removeu a contenção de layout de container-type, então ele não cria mais um contexto de empilhamento. O Chrome fez essa mudança na versão 129, e o Firefox e o Safari já seguiram o mesmo caminho.

Três casos comuns: sintoma, causa e correção

Sintoma: o overlay escurece a página, mas o header continua iluminado por cima dele. Causa: o modal é renderizado dentro de um wrapper que tem um transform, uma opacity ou um z-index posicionado, e o contexto do header fica acima desse wrapper. Correção: mova o modal para o <body> (veja as correções abaixo).

Um dropdown dentro de um card com transform tem um de dois problemas, empilhamento ou recorte, então primeiro identifique qual é o seu caso. Se o menu é pintado atrás do próximo card, o problema é de empilhamento: o card com transform é um contexto, e os cards que vêm depois no DOM são pintados por cima dele. Eleve o card aberto:

.card.is-open { position: relative; z-index: 1; }

Se o menu é cortado na própria borda do card, o problema é de recorte. Verifique o overflow do card e de seus ancestrais. Qualquer valor diferente de visible recorta o menu, e nenhum valor de z-index vai trazê-lo de volta.

Elemento sticky que não fica por cima

Sintoma: os cards deslizam por cima de um header sticky quando você passa o mouse ou rola a página. Causa: qualquer elemento com position: fixed ou sticky cria um contexto. Sem z-index, porém, ele fica no nível 0, e irmãos posteriores que criam contextos (como cards com transforms no hover) são pintados por cima dele porque vêm depois no DOM. Correção:

.site-header { position: sticky; top: 0; z-index: var(--z-sticky); }

Se o header nem chega a fixar, a causa geralmente é o overflow de algum ancestral. Para construir um header sticky completo, veja Building a responsive sticky navbar with CSS.

Três formas de corrigir um elemento aprisionado

Toda correção para um elemento aprisionado altera a estrutura dos contextos de empilhamento, e não o número do z-index: mova o elemento, mova a propriedade ou crie intencionalmente uma fronteira onde você quiser.

Mova o elemento no DOM. Um overlay que é filho direto de <body> não tem nenhum contexto ancestral acima dele, então seu z-index é comparado na raiz. É por isso que portals funcionam tão bem para overlays que precisam cobrir a página inteira:

const modal = document.querySelector('.modal');

function openModal() {
  document.body.appendChild(modal); // escape every ancestor context
  modal.hidden = false;
}

Remova ou realoque a propriedade. Andy Blum, da Lullabot, descreveu um caso em que um layout da direita para a esquerda (RTL) espelhava um contêiner de vídeo de fundo com um transform, o que soterrava o botão de play/pause do vídeo. Ele resolveu o problema movendo o transform do contêiner para o iframe do vídeo:

/* Before: the wrapper becomes a context */
[dir="rtl"] .hero { transform: scaleX(-1); }

/* After: only the media is mirrored */
[dir="rtl"] .hero iframe { transform: scaleX(-1); }

Adicione isolamento de propósito. Definir isolation: isolate na raiz de um componente cria um contexto deliberadamente, de modo que todo z-index dentro do componente permanece local:

.card { isolation: isolate; }
.card .badge { position: absolute; z-index: 1; } /* never competes with the site header */

Previna o problema com uma escala de z-index

Um z-index de 9999 quase sempre significa que alguém nunca encontrou o contexto de empilhamento, e não que precisava de um número maior. Substitua valores avulsos por uma escala pequena e documentada:

:root {
  --z-base: 0;
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-overlay: 300;
  --z-modal: 400;
  --z-toast: 500;
}
.modal { z-index: var(--z-modal); }

Os tokens da escala de z-index só ordenam elementos que compartilham um contexto de empilhamento. Por isso, eles funcionam melhor em conjunto com portals para as camadas no nível da página e com isolation: isolate nas raízes dos componentes.

Conclusão

Um z-index que não funciona é quase sempre um problema de contexto de empilhamento, e não um problema com o número. Da próxima vez que 9999 perder, deixe o valor como está. Abra o painel Layers ou o 3D View para confirmar o que está sendo pintado e onde e, em seguida, suba a partir dos dois elementos usando o checklist até que as duas cadeias se encontrem. Os dois filhos do ancestral em comum são os elementos que estão sendo comparados, e a correção deve ser aplicada em um deles.

Perguntas frequentes

Por que o z-index não tem nenhum efeito no meu elemento?

O z-index só se aplica a elementos posicionados, ou seja, com position definido como relative, absolute, fixed ou sticky, e a itens de flex ou grid. Em um elemento com o position: static padrão que não seja filho de flex ou grid, o navegador ignora o z-index completamente. Adicione position: relative para que o valor tenha efeito e, depois, verifique se algum contexto de empilhamento ancestral ainda o limita.

Um dialog aberto com showModal() escapa dos contextos de empilhamento?

Sim. Chamar showModal() em um elemento dialog o promove para a top layer, que fica acima de todos os contextos de empilhamento do documento. Transforms e valores de z-index dos ancestrais deixam de afetar sua ordem. Popovers exibidos com showPopover() são promovidos da mesma forma. O z-index não tem efeito dentro da top layer: os elementos são empilhados na ordem em que foram promovidos, então o aberto mais recentemente aparece por cima.

Por que meu overlay com position: fixed é posicionado em relação ao elemento pai e não à viewport?

Um ancestral com transform, perspective ou filter diferente de none se torna o bloco de contenção (containing block) dos descendentes com posição fixa. Um overlay fixo dentro dele passa então a ser posicionado e dimensionado em relação a esse ancestral, e não à viewport. A mesma propriedade também cria um contexto de empilhamento, então o overlay pode ficar atrás de outros conteúdos e cobrir a área errada ao mesmo tempo. Mover o overlay para o body resolve os dois problemas.

Um z-index negativo pode colocar um filho atrás do seu pai?

Somente quando o pai não cria um contexto de empilhamento. Dentro de um contexto de empilhamento, o navegador pinta primeiro o background e as bordas da própria raiz do contexto, depois os filhos com z-index negativo e, em seguida, o conteúdo em fluxo. Se o pai tiver opacity abaixo de 1, um transform ou um z-index diferente de auto com posicionamento, um filho com z-index: -1 ainda será pintado acima do background do pai, embora abaixo do seu conteúdo normal.

Open-source session replay

Gain control over your UX

See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.