z-index modal no funciona: solución definitiva
El escenario clásico: Tu modal tiene z-index: 9999 pero aparece detrás del header sticky de tu página. 😤
Te voy a explicar exactamente por qué ocurre y cómo solucionarlo.
¿Por qué el modal queda detrás?
El problema structurel
<body>
<header style="z-index: 100">
<!-- Tu header sticky -->
</header>
<div class="app-container" style="z-index: 1">
<!-- Aquí está tu botón que abre el modal -->
<button>Abrir Modal</button>
</div>
<div class="modal" style="z-index: 9999">
<!-- Modal - PERO APARECE DETRÁS DEL HEADER -->
</div>
</body>
El problema: El modal está limitado por el stacking context de .app-container.
Las 3 soluciones principales
Solución 1: position: fixed (La más simple)
Usa position: fixed para el modal. Esto lo sac del contexto del padre.
.modal {
position: fixed;
z-index: 9999;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
¿Por qué funciona?
position: fixedcrea su propio stacking context- Ya no está limitado por el padre
Solución 2: Portal/Portal (React/Vue/Angular)
La solución más limpia para SPAs es usar portals.
React:
import { createPortal } from 'react-dom';
function Modal({ children }) {
return createPortal(
<div className="modal-overlay">
{children}
</div>,
document.body // Se renderiza fuera de toda jerarquía
);
}
Vue 3:
<teleport to="body">
<div class="modal">Contenido</div>
</teleport>
Vanilla JS:
function openModal(htmlContent) {
const modal = document.createElement('div');
modal.className = 'modal';
modal.innerHTML = htmlContent;
document.body.appendChild(modal); // Se mueve fuera del padre
}
Solución 3: Mueve el HTML fuera del padre
Simple pero efectivo:
<!-- ❌ Dentro del contenedor principal -->
<main class="app">
<button onclick="openModal()">Abrir</button>
<div class="modal">...</div>
</main>
<!-- ✅ Fuera del app container -->
<main class="app">
<button onclick="openModal()">Abrir</button>
</main>
<div class="modal">...</div>
Ejemplo completo con backdrop
.modal-overlay {
position: fixed;
z-index: 9999;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
position: relative;
z-index: 10000; /* Mayor que el overlay */
background: white;
padding: 2rem;
border-radius: 8px;
max-width: 500px;
}
.header {
position: sticky;
z-index: 100; /* Ahora el modal overridea esto */
}
<!-- Estructura recomendada -->
<body>
<header class="header">Mi Header</header>
<main>
<button>Abrir Modal</button>
</main>
<!-- El modal vive directamente en body -->
<div class="modal-overlay">
<div class="modal-content">
Contenido del modal
</div>
</div>
</body>
Problema común: backdrop detrás del modal
Síntoma: El backdrop se ve pero el contenido del modal está detrás.
/* ❌ El backdrop tiene más z-index */
.modal-backdrop {
position: fixed;
z-index: 9998;
}
.modal-content {
position: relative;
z-index: 9999; /* Mayor que backdrop - CORRECTO */
}
Tabla de soluciones
| Escenario | Solución |
|---|---|
| Modal dentro de contenedor con z-index | position: fixed o Portal |
| Header sticky supera al modal | Portal o mover HTML |
| Backdrop sobre contenido | Añadir z-index: +1 al contenido vs backdrop |
| Múltiples modales | Cada modal en body, z-index incremental |
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?
Artículos relacionados
- Problemas z-index CSS: Guía completa
- z-index no funciona: 5 soluciones
- Entiende el stacking context
- Dropdown menú z-index problemas
- z-index con overflow:hidden
: El modal quiere estar libre. Ayúdalo a escapar del contexto de su padre.