Saltar al contenido
z-index.css · devschool

z-index y contextos de apilamiento en CSS

Lección 15 de 50 · 10 min de lectura · Actualizado el

En esta lección
  1. El eje Z
  2. El orden natural, sin z-index
  3. z-index necesita position (o casi)
  4. El contexto de apilamiento
  5. Qué crea un contexto nuevo
  6. isolation: isolate: crear un contexto a propósito
  7. La capa superior: modales sin z-index
  8. Cómo depurar un z-index que no funciona
  9. Una escala ordenada con variables
  10. Errores frecuentes
  11. Resumen

La página parece plana, pero los elementos se dibujan unos encima de otros, como hojas de papel sobre una mesa. z-index decide qué hoja queda arriba. Parece sencillo hasta que pones z-index: 9999 a un menú y sigue quedando debajo de un banner. En esta lección vas a entender por qué pasa: qué es un contexto de apilamiento, qué propiedades lo crean sin que te des cuenta, cómo usar isolation: isolate y cómo organizar los valores para no volver a pelearte con ellos.

En posicionamiento viste lo básico de z-index. Aquí lo vemos con calma.

El eje Z

Además del ancho (eje X) y el alto (eje Y), la página tiene un tercer eje imaginario, el eje Z, que sale de la pantalla hacia ti. z-index coloca un elemento en ese eje: cuanto mayor el número, más cerca de ti y más “arriba”.

.menu-desplegable { position: absolute; z-index: 10; }
.aviso-cookies    { position: fixed;    z-index: 20; }

Si dos elementos se solapan, el de mayor z-index tapa al otro. Admite números enteros, también negativos, y el valor auto (por defecto).

El orden natural, sin z-index

Antes de tocar nada, el navegador ya sigue un orden. Dentro de un mismo grupo, de abajo arriba:

  1. El fondo y los bordes del elemento que contiene el grupo.
  2. Los hijos con z-index negativo.
  3. Los elementos normales (no posicionados), en el orden del HTML.
  4. Los elementos flotantes (float).
  5. El texto y los elementos en línea.
  6. Los elementos posicionados (relative, absolute, fixed, sticky) con z-index: auto o 0, en el orden del HTML.
  7. Los elementos con z-index positivo, de menor a mayor.

Dos consecuencias prácticas:

  • Un elemento posicionado queda por encima de uno normal aunque ninguno tenga z-index. Por eso, a veces, poner position: relative a algo hace que “salte” por encima de sus vecinos.
  • Entre elementos del mismo nivel, gana el que aparece más tarde en el HTML.

z-index necesita position (o casi)

z-index no hace nada en un elemento con position: static, que es el valor por defecto:

.tarjeta { z-index: 5; }                       /* no tiene efecto */
.tarjeta { position: relative; z-index: 5; }   /* ahora sí */

position: relative sin top ni left no mueve el elemento, así que es la forma habitual de “activar” el z-index sin cambiar nada más.

La excepción son los hijos de un contenedor flex o grid: en ellos z-index funciona aunque sean static. Si en una rejilla de fotos quieres que la que pasa a primer plano quede encima, no hace falta position:

.galeria { display: grid; }
.galeria img:hover { z-index: 1; scale: 1.2; }

El contexto de apilamiento

Aquí está la clave de todo. Un contexto de apilamiento es un grupo de elementos que se ordenan entre sí, y que hacia fuera se comporta como una sola capa.

Piensa en carpetas sobre una mesa. Dentro de cada carpeta hay hojas ordenadas del 1 al 9999. Pero si la carpeta A está debajo de la carpeta B, todas las hojas de A quedan debajo de B, aunque una hoja de A tenga el número 9999 y la carpeta B solo tenga un 2. El número de la hoja solo sirve dentro de su carpeta.

<header class="cabecera">
  <ul class="menu">...</ul>
</header>
<div class="banner">...</div>
.cabecera { position: relative; z-index: 1; }   /* carpeta con nivel 1 */
.menu     { position: absolute; z-index: 9999; } /* hoja 9999, pero DENTRO de la carpeta 1 */
.banner   { position: relative; z-index: 2; }   /* carpeta con nivel 2 */

El menú se despliega sobre el banner… y queda debajo. Su 9999 solo compite con los otros hijos de .cabecera. Hacia fuera, lo que cuenta es el 1 de la cabecera contra el 2 del banner. Este es el famoso “z-index: 9999 no funciona”. Subirlo a 999999 tampoco funcionaría.

La solución es comparar al nivel correcto: subir la cabecera, no el menú.

.cabecera { position: relative; z-index: 3; }   /* ahora toda la carpeta va encima */

En el ejemplo del editor tienes las dos versiones lado a lado.

Qué crea un contexto nuevo

Esta es la parte traicionera: muchas propiedades que no tienen nada que ver con capas crean un contexto nuevo sin avisar. Las más habituales:

PropiedadCrea contexto cuando…
position: relative / absoluteTiene un z-index distinto de auto
position: fixed / stickySiempre
Hijo de flex o gridTiene un z-index distinto de auto
opacityEs menor que 1
transform, translate, rotate, scaleDistinto de none
filter, backdrop-filterDistinto de none
mix-blend-modeDistinto de normal
clip-path, maskDistinto de none
isolationEs isolate
will-changeNombra alguna de las anteriores
containlayout, paint, strict o content
container-typesize o inline-size

Y el propio <html> es el contexto raíz de toda la página.

Un caso real: añades a una tarjeta un efecto transform: translateY(-4px) al pasar el ratón. De repente, el menú de opciones que esa tarjeta tiene dentro deja de verse sobre la tarjeta de abajo. El transform ha convertido la tarjeta en una carpeta con nivel auto, y la siguiente tarjeta, que va después en el HTML, la tapa.

.tarjeta { position: relative; }
.tarjeta:hover { transform: translateY(-4px); z-index: 1; } /* súbela mientras está activa */

isolation: isolate: crear un contexto a propósito

A veces quieres justo lo contrario: encerrar un grupo de capas para que no interfieran con el resto. isolation: isolate crea un contexto nuevo sin efectos secundarios: no mueve nada, no cambia el aspecto y no necesita un número de z-index.

El uso más típico es un adorno detrás del texto con z-index: -1:

.etiqueta {
  position: relative;
  isolation: isolate;    /* sin esto, el adorno se va detrás del fondo de la página */
}
.etiqueta::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;           /* detrás del texto de la etiqueta */
  background: #fde68a;
  border-radius: 999px;
}

Sin isolation, el z-index: -1 manda el adorno al fondo del contexto más cercano, que puede ser la página entera: quedaría escondido detrás del fondo de una sección. Con isolate, la etiqueta es su propio contexto y el -1 significa “detrás del texto, pero dentro de la etiqueta”.

También es útil en componentes: si cada tarjeta, modal o widget lleva isolation: isolate, los z-index de dentro nunca chocarán con los del resto de la página.

La capa superior: modales sin z-index

Hay una capa que está por encima de todo, sin importar los contextos: la capa superior (top layer). Llegan a ella:

  • Un <dialog> abierto con showModal().
  • Un elemento con el atributo popover cuando está abierto.
  • Un elemento a pantalla completa.
<dialog id="confirmar">¿Seguro que quieres cancelar la reserva?</dialog>

Si usas <dialog> para tus modales (lo tienes en details y dialog), no necesitas ningún z-index para que queden encima: ningún contexto los atrapa. Es la mejor solución al problema de las capas.

Cómo depurar un z-index que no funciona

Cuando un elemento se empeña en quedarse debajo, sigue estos pasos:

  1. ¿Tiene position o es hijo de flex/grid? Si es static, el z-index se ignora. En las DevTools de Firefox, la regla aparece en gris y, al pasar el ratón, explica por qué no tiene efecto.
  2. ¿Con quién compite de verdad? Sube por sus antepasados en la pestaña Elementos y busca el primero que cree un contexto: z-index con position, opacity, transform, filter… Revisa la pestaña Computed de cada uno.
  3. Compara al mismo nivel. Encuentra el antepasado del elemento y el antepasado del que lo tapa que son hermanos dentro del mismo contexto. Esos son los que tienes que ordenar.
  4. Prueba en vivo. Cambia el z-index del antepasado desde las DevTools hasta que funcione, y luego llévalo a tu CSS.

Chrome y Edge tienen además un panel Layers (en Edge, 3D View) que dibuja las capas de la página en tres dimensiones. Lo abres desde el menú de las DevTools, en “Más herramientas”.

Una escala ordenada con variables

Si cada vez que algo queda debajo le sumas un cero (10, 100, 9999, 99999…), en unos meses nadie sabrá qué va encima de qué. La solución es definir una escala en un solo sitio, con variables CSS:

:root {
  --z-base: 1;
  --z-desplegable: 100;
  --z-cabecera-fija: 200;
  --z-capa-oscura: 300;
  --z-modal: 400;
  --z-aviso: 500;
}

.menu-usuario { position: absolute; z-index: var(--z-desplegable); }
.cabecera     { position: sticky; top: 0; z-index: var(--z-cabecera-fija); }
.toast        { position: fixed; bottom: 16px; z-index: var(--z-aviso); }

Ventajas:

  • Los nombres dicen qué es cada capa, no un número mágico.
  • El orden de toda la web se ve de un vistazo.
  • Los saltos de 100 en 100 dejan hueco para añadir capas intermedias.
  • Dentro de un componente aislado con isolation: isolate, basta con 1, 2, 3: esos números no salen fuera.

Errores frecuentes

  • Poner z-index a un elemento static. No hace nada. Añade position: relative, salvo que sea hijo de flex o grid.
  • Subir el número del hijo. Si el problema es el contexto del padre, da igual poner 9999 o un millón. Ordena el antepasado que compite.
  • No ver el contexto que crean opacity, transform o filter. Un efecto de :hover puede cambiar las capas. Revisa los antepasados en las DevTools.
  • z-index: -1 que desaparece. Se ha ido detrás del fondo de un antepasado. Pon isolation: isolate en el padre directo.
  • Números al azar. z-index: 999, 9999, 99999 repartidos por el CSS acaban en una guerra. Usa una escala con variables.
  • Luchar contra las capas en los modales. Usa <dialog> con showModal(): va a la capa superior.

Resumen

ConceptoQué significa
z-indexPosición en el eje Z: mayor número, más arriba
Requisitoposition distinto de static, o ser hijo de flex/grid
Sin z-indexLos posicionados van encima; a igualdad, el último del HTML
Contexto de apilamientoGrupo que se ordena por dentro y fuera es una sola capa
Lo creanz-index con position, fixed, sticky, opacity < 1, transform, filter…
“9999 no funciona”El padre está en un contexto más bajo: ordena el antepasado
isolation: isolateCrea un contexto a propósito, sin efectos visuales
Capa superior<dialog> modal y popover: encima de todo, sin z-index
Escala con variables--z-desplegable, --z-modal… en :root

Ya controlas las capas. Sigue con una de las preguntas más famosas de CSS: cómo centrar elementos.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

z-index.html
Resultado

Pon a prueba lo que has aprendido

[CSS] Un hijo tiene z-index: 9999 y sigue detrás de otro elemento. ¿Qué es lo más probable?

[CSS] ¿Cuál de estas propiedades crea un nuevo contexto de apilamiento?

[CSS] Un elemento con position: static y z-index: 10. ¿Qué efecto tiene el z-index?

[CSS] ¿Qué ventaja tiene <dialog> con showModal() respecto a las capas?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.