La capa superior y el final del z-index
Qué es el top layer, cómo se entra y se sale de él, por qué hace irrelevante el z-index para lo que está dentro, y qué es exactamente ::backdrop.
La capa superior no es un z-index muy alto. Es un contenedor de renderizado que existe fuera del árbol de pintado del documento, definido en la propia especificación de CSS de posicionamiento, y en el que el navegador coloca ciertos elementos por orden de llegada. Un elemento que está ahí se pinta por encima de absolutamente todo lo demás, sin depender de ningún contexto de apilamiento ni de ningún bloque contenedor intermedio. Es la respuesta de la plataforma a dos décadas de guerras de números.
- Describir qué es la capa superior y cómo se ordena su contenido.
- Enumerar las tres formas de entrar en ella.
- Explicar por qué el
z-indexno tiene efecto entre sus elementos. - Estilar el fondo de un elemento de la capa superior con
::backdrop.
Qué es exactamente
Cada documento tiene, además de su árbol de cajas normal, una capa superior: una pila de elementos que se pintan después de todo el contenido del documento, en el orden en que entraron. El último que entra queda por encima.
Un elemento promocionado a la capa superior sigue estando en el DOM donde lo pusiste. Hereda los estilos de sus antepasados, participa en el orden de lectura del documento, sus selectores siguen funcionando. Lo único que cambia es dónde se pinta: sale del árbol de pintado normal y pasa a la pila.
Esa separación es la que resuelve el problema. Todo lo que la lección sobre por qué tu z-index de 9999 sigue quedando debajo diagnostica —contextos de apilamiento que encierran, bloques contenedores que roban el viewport, overflow que recorta— ocurre dentro del árbol de pintado. Un elemento que no está ahí no puede sufrirlo.
Hay tres formas de entrar:
- Un
dialogabierto conshowModal(). - Un elemento con el atributo
popovermostrado. - Un elemento en pantalla completa, vía
requestFullscreen().
Y ninguna otra. No hay una propiedad CSS que promocione un elemento arbitrario a la capa superior; la entrada la controla siempre el HTML o una API del DOM. Esa restricción es deliberada: si fuera una propiedad de estilo, cualquier hoja de terceros podría promocionar cualquier cosa y volveríamos al mismo problema con otro nombre.
Por qué el z-index deja de importar
Dentro de la capa superior, el orden lo determina exclusivamente el momento de entrada. z-index no se ignora formalmente —cada elemento de la capa superior forma su propio contexto de apilamiento, y dentro de él el z-index de sus descendientes funciona con normalidad— pero no sirve para ordenar dos elementos de la capa superior entre sí.
<dialog id="a" style="z-index: 1">Abro primero</dialog>
<dialog id="b" style="z-index: 999999">Abro después</dialog>
a.showModal();
b.showModal(); // queda por encima de a, pese al z-index
Si cierras b y lo vuelves a abrir, vuelve a quedar arriba. El orden es una pila y se comporta como una pila.
Esto tiene una consecuencia práctica excelente: el orden de apilamiento de tus superposiciones deja de ser un problema de mantenimiento. Un modal abierto desde otro modal queda encima porque se abrió después, no porque alguien recordara subirle el número. Un menú abierto desde dentro de un modal queda encima del modal por la misma razón.
flowchart TB A[Arbol de pintado del documento] --> B[Contextos de apilamiento anidados] B --> C[Aqui z-index ordena y los contextos encierran] D[Capa superior] --> E[Pila ordenada por momento de entrada] E --> F[Aqui z-index no ordena entre elementos] G[El navegador pinta primero el documento] --> A G --> H[Y despues la capa superior encima de todo] H --> D style A fill:#89b4fa,color:#11111b style C fill:#f9e2af,color:#11111b style D fill:#a6e3a1,color:#11111b style F fill:#a6e3a1,color:#11111b style H fill:#cba6f7,color:#11111b
Un matiz que conviene tener claro: la promoción a la capa superior no cambia el bloque contenedor del elemento por sí sola. Lo que ocurre en la práctica es que las hojas de estilo de usuario del navegador dan a dialog y a los elementos con popover un position: fixed cuando están abiertos, y eso sí los ancla al viewport. Si sobrescribes esa posición con position: absolute, el elemento vuelve a depender de su bloque contenedor para la geometría, aunque siga pintándose encima de todo. Es exactamente lo que quieres cuando lo anclas a otro elemento, y es la base del patrón de la última lección de este nivel.
::backdrop
::backdrop es un pseudo-elemento que existe únicamente para los elementos de la capa superior. Es una caja del tamaño del viewport que se pinta justo por debajo del elemento y por encima de todo lo demás.
dialog::backdrop {
background: oklch(0% 0 0 / .5);
backdrop-filter: blur(2px);
}
Cuatro cosas que hay que saber de él y que casi nunca se cuentan juntas:
Hereda del elemento que lo origina, pero eso es reciente. Durante años ::backdrop no heredaba de nada: era un huérfano en el árbol de herencia, y las custom properties de tu sistema de diseño no llegaban a él. Escribir background: var(--velo) no resolvía. La especificación se corrigió y los motores lo implementaron entre finales de 2023 y principios de 2024: Firefox 120, Chrome 122 y Safari 17.4. Desde entonces ::backdrop hereda de su elemento originador y las variables sí llegan. Si tienes que soportar navegadores anteriores, declara los valores literalmente en la regla del pseudo-elemento.
Solo existe si el elemento está en la capa superior. Un dialog abierto con show() en lugar de showModal() no genera ::backdrop, porque no entra en la capa superior. Un popover abierto sí lo genera, aunque su estilo por defecto sea transparente.
Hay uno por elemento, no uno global. Con dos modales abiertos hay dos ::backdrop apilados. Si los dos son semitransparentes, el oscurecimiento se acumula. Suele ser lo que quieres, pero conviene saberlo.
Es animable como cualquier otro elemento. Puedes darle una transición de opacidad, y con @starting-style también animar su entrada, que es el tema de la quinta lección.
Un valor por defecto que conviene conocer: el ::backdrop de un dialog modal viene con un fondo semitransparente en la hoja de usuario, y el de un popover viene transparente. Por eso un popover no oscurece la página y un modal sí.
El valor de la capa superior que menos se menciona, y que es el que de verdad importa, es la accesibilidad. Piensa en cómo se construía un modal antes: un div con position: fixed y z-index alto, montado en un portal como hijo de body. Ese div está visualmente por encima de todo, pero para el resto de la plataforma no es especial en absoluto. El contenido de detrás sigue siendo enfocable con tabulador, sigue siendo leído por los lectores de pantalla, sigue respondiendo a los clics si tu velo no los intercepta bien. Toda la corrección tenía que escribirse a mano: capturar el foco con un ciclo de tabulación, poner inert o aria-hidden en el resto del documento y acordarse de quitarlo, escuchar la tecla de escape, guardar el elemento que tenía el foco antes para devolvérselo al cerrar, impedir el scroll de fondo. Cinco mecanismos independientes que había que mantener sincronizados con el estado visual, y que en la práctica se implementaban mal o a medias en la mayoría de los sitios. La capa superior acopla las dos cosas por definición: showModal() promociona, aísla el resto del documento con inercia, mueve el foco, atiende la tecla de escape y devuelve el foco al cerrar, todo como una sola operación atómica que no se puede desincronizar. La conclusión no es “ahora es más cómodo”: es que el estado visual y el estado de interacción dejaron de ser dos cosas que un desarrollador podía dejar en desacuerdo, y esa clase entera de fallos de accesibilidad desapareció.
- Abre dos
dialogmodales seguidos conz-indexinvertidos y comprueba cuál queda encima. - Cierra el segundo y vuelve a abrirlo; verifica que vuelve a la cima de la pila.
- Abre un
dialogconshow()y comprueba que no genera::backdrop. - Declara
background: var(--velo)en un::backdropcon la variable definida en el propiodialogy comprueba que hereda. - Anida un modal dentro de un contenedor con
transformy confirma que sigue cubriendo la pantalla entera.