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: fixed crea 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

EscenarioSolución
Modal dentro de contenedor con z-indexposition: fixed o Portal
Header sticky supera al modalPortal o mover HTML
Backdrop sobre contenidoAñadir z-index: +1 al contenido vs backdrop
Múltiples modalesCada modal en body, z-index incremental

Checklist para modales

  • ¿El modal tiene position: fixed?
  • ¿Está renderizado en document.body o nivel superior?
  • ¿El contenido del modal tiene mayor z-index que el overlay?
  • ¿Algún padre tiene opacity o transform?

Artículos relacionados


: El modal quiere estar libre. Ayúdalo a escapar del contexto de su padre.