Posicionamiento y stacking context
El posicionamiento es donde más bugs silenciosos viven en CSS. El z-index que no funciona, el sticky que se queda pegado donde no toca, el elemento que aparece detrás de otro sin razón aparente. Todo tiene una explicación lógica.
Repaso: los tipos de position
position: static; /* por defecto — flujo normal, no acepta top/left/z-index */
position: relative; /* en el flujo, pero puedes desplazarlo con top/left */
position: absolute; /* sale del flujo, se posiciona respecto al ancestro posicionado más cercano */
position: fixed; /* sale del flujo, se posiciona respecto al viewport */
position: sticky; /* híbrido: flujo normal hasta alcanzar el threshold, luego se "pega" */
El punto crítico: absolute busca hacia arriba en el DOM hasta encontrar un ancestro con position distinto de static. Si no encuentra ninguno, se posiciona respecto al <html>. Por eso poner position: relative en el contenedor padre es tan habitual.
position: sticky — cómo funciona de verdad
sticky es el más malentendido. No es simplemente “se pega al scroll”. Tiene reglas específicas:
.header {
position: sticky;
top: 0; /* el umbral: a qué distancia del borde se "pega" */
}
Para que funcione correctamente:
- Necesita
top,bottom,leftoright— sin estos valores, se comporta comorelative. - El contenedor padre no puede tener
overflow: hiddenooverflow: auto— esto es el bug más frecuente. Si su padre tiene overflow, el sticky queda confinado y deja de funcionar. - Solo es sticky dentro de su contenedor padre — cuando el padre sale del viewport, el elemento sticky lo sigue. Si el sidebar es sticky pero su contenedor termina antes, el sidebar también termina.
/* Bug frecuente — overflow rompe sticky */
.contenedor {
overflow: hidden; /* ← esto mata el sticky de los hijos */
}
.sidebar {
position: sticky;
top: 80px; /* no funciona por el overflow del padre */
}
z-index — el orden de apilamiento
z-index solo funciona en elementos con position distinto de static (o en elementos flex/grid). En static, z-index no tiene efecto, no importa el número que pongas.
/* z-index funciona en: */
.btn { position: relative; z-index: 10; } /* ✅ */
.modal { position: fixed; z-index: 100; } /* ✅ */
.item { display: flex; z-index: 5; } /* ✅ en contexto flex/grid */
/* z-index NO funciona en: */
.texto { z-index: 9999; } /* ❌ — position: static por defecto */
Stacking context — el concepto que nadie explica
El stacking context (contexto de apilamiento) es el concepto que explica por qué tu z-index: 9999 no sirve de nada a veces. Cuando un elemento crea un stacking context, todos sus hijos viven dentro de ese contexto y no pueden salir de él, independientemente del z-index que tengan.
Es como si cada stacking context fuera una capa de papel: el z-index de los elementos dentro de esa capa solo determina el orden dentro de la capa. La capa completa compite con otras capas por su z-index.
Qué crea un stacking context:
position: relative/absolute/fixed/sticky+ cualquierz-indexque no seaautoopacitymenor de 1transform,filter,clip-path,maskwill-changecon alguna de las propiedades anterioresisolation: isolate(creado específicamente para esto)mix-blend-modedistinto denormal
<div class="capa-A" style="position: relative; z-index: 1">
<div class="hijo" style="position: relative; z-index: 9999">
<!-- Este z-index:9999 solo compite DENTRO de capa-A -->
<!-- Nunca estará por encima de capa-B si capa-B tiene z-index:2 -->
</div>
</div>
<div class="capa-B" style="position: relative; z-index: 2">
<!-- capa-B está por encima de capa-A completa, incluyendo sus hijos -->
</div>
isolation: isolate — stacking contexts controlados
Si quieres que un componente tenga su propio contexto de apilamiento interno sin tener que asignar un z-index concreto, usa isolation: isolate:
.modal-wrapper {
isolation: isolate; /* crea stacking context sin especificar z-index */
}
.modal-wrapper .overlay {
z-index: 1; /* estos z-index son locales al modal-wrapper */
}
.modal-wrapper .dialog {
z-index: 2;
}
Esto evita que los z-indexes internos de un componente interfieran con el resto de la página.
Estrategia de z-index: escala por capas
En vez de asignar valores al azar, define una escala con variables:
:root {
--z-base: 0;
--z-dropdown: 10;
--z-sticky: 20;
--z-overlay: 30;
--z-modal: 40;
--z-toast: 50;
--z-tooltip: 60;
}
.header {
position: sticky;
top: 0;
z-index: var(--z-sticky);
}
.modal-backdrop {
position: fixed;
inset: 0;
z-index: var(--z-overlay);
}
.modal {
position: fixed;
z-index: var(--z-modal);
}
inset — shorthand de top/right/bottom/left
inset es el shorthand moderno para los cuatro valores posicionales:
/* Estos dos bloques son equivalentes */
.overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.overlay {
position: absolute;
inset: 0; /* los cuatro a 0 */
}
/* También funciona con valores distintos */
inset: 1rem; /* todos iguales */
inset: 1rem 2rem; /* top/bottom | left/right */
inset: 1rem 2rem 0; /* top | left/right | bottom */
inset: 1rem 2rem 0 3rem; /* top | right | bottom | left */
En la siguiente lección dominamos las funciones CSS: calc(), clamp(), min() y max() para crear valores dinámicos y responsive sin necesidad de media queries.