z-index no funciona: 5 soluciones definitivas
“Le puse z-index: 9999 y sigue detrás” 😤 Si esto te suena familiar, este artículo es para ti.
El problema de z-index no suele ser el valor que asignas, sino el contexto de apilamiento que estás ignorando.
La causa raíz: stacking context
Cada elemento HTML crea su propio contexto de apilamiento. Los elementos hijos solo pueden estar dentro de ese contexto, nunca salir de él.
Esto significa:
- Aunque tu hijo tenga
z-index: 9999 - Si su padre tiene
z-index: 1 - El hijo nunca podrá estar encima de elementos externos al padre
Las 5 soluciones
Solución 1: Añade position al elemento
z-index solo funciona con elementos posicionados.
/* ❌ NO funciona */
.boton-flotante {
z-index: 100;
}
/* ✅ CORRECTO */
.boton-flotante {
position: relative;
z-index: 100;
}
Valores válidos de position: relative, absolute, fixed, sticky
Solución 2: Saca al hijo del contexto del padre
Si el padre está limitando a tu hijo, tienes opciones:
Opción A: Mueve el HTML
<!-- ❌ El dropdown está dentro de .header -->
<div class="header">
<div class="dropdown">...</div>
</div>
<!-- ✅ El dropdown está fuera -->
<div class="header"></div>
<div class="dropdown">...</div>
Opción B: Usa Portal (React/Vue)
// React - Renderiza fuera del padre
import { createPortal } from 'react-dom';
return createPortal(
<Dropdown />,
document.body
);
Solución 3: Remueve propiedades que crean nuevo contexto
Estas propiedades crean stacking context automáticamente:
opacitymenor a 1transform(cualquier valor exceptonone)filterisolation: isolateposition: fixed
/* ❌ Crea nuevo contexto */
.caja {
opacity: 0.9;
transform: scale(1.1);
}
/* ✅ Alternativas sin crear contexto */
.caja {
background: rgba(255, 255, 255, 0.9);
/* transform eliminado */
}
Solución 4: Usa position: fixed para overlays
Para modales y popups, position: fixed es más confiable:
.modal-overlay {
position: fixed;
z-index: 9999;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.modal-contenido {
position: relative;
z-index: 10000; /* Mayor que el overlay */
}
Solución 5: Establece el z-index del padre
A veces, solo necesitas darle al padre el z-index correcto:
.header {
position: relative;
z-index: 100;
}
.dropdown {
position: absolute;
z-index: 200; /* Ahora funciona */
top: 100%;
}
Ejemplo completo: Modal que no aparece
El problema
<div class="modal-container"> <!-- z-index: 1 -->
<div class="modal"> <!-- z-index: 9999 - NO FUNCIONA -->
Contenido
</div>
</div>
La solución
<div class="modal-container">
<!-- El modal se rendered en document.body via portal -->
</div>
<!-- O directamente: -->
<div class="modal">
Contenido
</div>
.modal {
position: fixed; /* Crea contexto independiente */
z-index: 9999;
/* Centrado */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
Tabla de referencias rápidas
| Problema | Solución |
|---|---|
| z-index no tiene efecto | Añadir position: relative/absolute/fixed/sticky |
| Hijo detrás de elementos externos | Remover el padre del flujo o usar portal |
| z-index no supera a otros | Verificar z-index del padre |
| Elementos se cortan | Revisar overflow: hidden del padre |
| Modal detrás de header | Usar position: fixed en el modal |
| Dropdown detrás del contenido | Subir el z-index del navbar o usar portal |
| Backdrop sobre el contenido del modal | Añadir z-index: +1 al contenido vs backdrop |
Problemas específicos: dropdowns
¿Por qué el dropdown queda atrás?
El dropdown está limitado por el z-index de su padre (navbar o header), aunque uses z-index: 9999 dentro:
<nav class="navbar" style="z-index: 10">
<div class="menu-container">
<button>Menú</button>
<div class="dropdown">
<!-- Este dropdown NO puede salir del navbar -->
Opciones...
</div>
</div>
</nav>
<main style="z-index: 1">
<div class="card">Producto 1</div>
<div class="card">Producto 2</div>
</main>
Soluciones para dropdowns
/* Solución 1: sube el z-index del padre */
.navbar {
position: relative;
z-index: 1000;
}
.dropdown {
position: absolute;
z-index: 1001; /* Más alto que el navbar */
}
/* Solución 2: position: fixed para menús flotantes.
Precaución: puede dar problemas con scroll en páginas largas. */
.dropdown {
position: fixed;
z-index: 9999;
top: 100%;
left: 0;
}
// Solución 3: portal para renderizar el dropdown en document.body
import { createPortal } from 'react-dom';
function Dropdown({ isOpen, children }) {
if (!isOpen) return null;
return createPortal(
<div className="dropdown">{children}</div>,
document.body
);
}
Sticky header + dropdown
Si tienes un header sticky, el dropdown debe tener un z-index mayor que el header:
.sticky-header {
position: sticky;
z-index: 100;
}
.dropdown {
z-index: 200; /* Debe ser mayor que el header */
}
Checklist para dropdowns
- ¿El navbar/header tiene z-index?
- ¿El dropdown tiene mayor z-index que su padre?
- ¿Hay
overflow: hiddencortando el dropdown? - ¿Usaste
position: fixedo un portal si es necesario?
Problemas específicos: modales
¿Por qué el modal queda detrás del header?
El modal está limitado por el stacking context de .app-container, incluso con z-index: 9999:
<body>
<header style="z-index: 100"></header>
<div class="app-container" style="z-index: 1">
<button>Abrir Modal</button>
</div>
<div class="modal" style="z-index: 9999">
<!-- PERO APARECE DETRÁS DEL HEADER -->
</div>
</body>
Soluciones para modales
/* Solución 1: position: fixed crea su propio stacking context */
.modal {
position: fixed;
z-index: 9999;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
// Solución 2: portal en React
import { createPortal } from 'react-dom';
function Modal({ children }) {
return createPortal(
<div className="modal-overlay">{children}</div>,
document.body
);
}
<!-- Solución 2b: teleport en Vue 3 -->
<teleport to="body">
<div class="modal">Contenido</div>
</teleport>
// Solución 2c: mover el HTML a body con vanilla JS
function openModal(htmlContent) {
const modal = document.createElement('div');
modal.className = 'modal';
modal.innerHTML = htmlContent;
document.body.appendChild(modal);
}
<!-- Solución 3: mueve el HTML fuera del padre -->
<main class="app">
<button onclick="openModal()">Abrir</button>
</main>
<div class="modal">...</div> <!-- ✅ fuera del app container -->
Backdrop detrás del modal
El contenido del modal debe tener un z-index mayor que su backdrop:
.modal-backdrop {
position: fixed;
z-index: 9998;
}
.modal-content {
position: relative;
z-index: 9999; /* Mayor que backdrop - CORRECTO */
}
Checklist para modales
- ¿El modal tiene
position: fixed? - ¿Está renderizado en
document.bodyo nivel superior? - ¿El contenido del modal tiene mayor z-index que el overlay?
- ¿Algún padre tiene
opacityotransform?
Problemas específicos: overflow: hidden
¿Por qué overflow:hidden corta elementos?
overflow: hidden actúa como una barrera que recorta todo lo que sale de los límites del contenedor, incluyendo elementos posicionados:
.card-container {
overflow: hidden;
}
.badge {
position: absolute;
top: -10px;
right: -10px;
}
/* El badge aparece cortado */
Las soluciones
/* Solución 1: usa overflow: visible (la más simple) */
.card {
overflow: visible; /* Permite que el badge se vea */
}
/* Solución 2: expande el contenedor y da espacio al elemento */
.card {
overflow: hidden;
padding: 10px; /* Espacio para el elemento posicionado */
}
<!-- Solución 3: mueve el elemento fuera del contenedor cortado -->
<div class="card-wrapper" style="position: relative">
<div class="card" style="overflow: hidden">
<img src="producto.jpg" />
</div>
<span class="badge">Oferta</span> <!-- ✅ fuera del contenedor -->
</div>
/* Solución 4: cuidado con overflow:hidden en flex/grid containers */
.flex-container {
display: flex;
overflow: visible; /* En vez de hidden */
}
Errores comunes con overflow
/* ❌ Pensar que z-index lo arreglará */
.badge {
position: absolute;
z-index: 9999; /* Ignorado si el padre tiene overflow:hidden */
}
/* ❌ Aplicar overflow:hidden a todo */
* { overflow: hidden; } /* Ahórrate el "too much clipping" */
Checklist para overflow
- ¿El padre tiene
overflow: hidden? - ¿Puedes usar
overflow: visible? - ¿Puedes mover el elemento fuera del padre?
- ¿Expandir el padding del padre ayuda?
Errores comunes con z-index
❌ z-index: 999999
Poner números excesivamente altos no ayuda. Usa una escala lógica:
.dropdown { z-index: 10; }
.sticky-nav { z-index: 100; }
.modal { z-index: 1000; }
.toast { z-index: 2000; }
❌ Ignorar el orden de apilamiento por defecto
El navegador apila elementos en un orden específico incluso sin z-index:
- Elementos con
position: static(fondo de la página) - Elementos flotantes
- Elementos con
position: staticen línea - Elementos posicionados (aquí entra z-index)
❌ Mezclar unidades de z-index
Todos los elementos deberían usar la misma escala para evitar confusiones.
Guía paso a paso para debuggear z-index
- Verifica que tenga position: asegúrate de
position: relative,absolute,fixedosticky. - Inspecciona el padre: el padre puede estar limitando el z-index del hijo.
- Busca propiedades problemáticas: revisa
opacity,transform,filteren los padres. - Usa DevTools: en Chrome activa el panel “Layers” para visualizar las capas.
- Prueba con valores extremos: si
z-index: 1no funciona, el problema no es el valor.
Artículos relacionados
: El número en z-index no es la respuesta. El contexto lo es.
Referencias
- z-index: la propiedad CSS para controlar el apilamiento de elementos.
- Entendiendo z-index y el contexto de apilamiento: por qué el stacking context limita al elemento hijo.
- position: los valores de posicionamiento que habilitan z-index.