Posicionamiento CSS y Sistemas de Coordenadas
La mayoria de tutoriales te dicen que hace position: absolute. Este te muestra como piensa -- como un sistema de coordenadas con origen, ejes y containing block.
Demo en Vivo: Cambiar Modos de Posicionamiento
Elige un valor de position y arrastra los controles deslizantes. La caja roja se reposiciona y la lectura de coordenadas te muestra exactamente donde esta su punto de origen y como top/left se traducen a desplazamientos de pixeles. Esta es la forma mas rapida de interiorizar la diferencia entre relative y absolute.
Posicionamiento como Sistema de Coordenadas, No Regla de Layout
Aqui esta el cambio mental que hace que el posicionamiento CSS haga clic: top, right, bottom y left son coordenadas, no margenes. Cuando escribes top: 50px, estas colocando el elemento 50 pixeles por debajo de un punto de origen especifico. La pregunta es: origen de que?
Cada elemento posicionado vive dentro de un containing block (bloque contenedor) -- la caja que define su origen de coordenadas. Piensalo como el punto (0, 0) para ese elemento. El containing block es diferente segun el valor de position que uses, y esa es la cosa mas confusa del posicionamiento CSS.
Para position: absolute y position: fixed, el elemento se elimina por completo del flujo normal del documento. Sus coordenadas se miden contra el containing block, no contra sus hermanos o padre en el flujo normal. Por eso un elemento absolutamente posicionado puede superponer otro contenido -- ha dejado el sistema de coordenadas del flujo del documento.
Los Cinco Valores de position, Desde una Perspectiva de Coordenadas
| Valor | Origen de Coordenadas (Containing Block) | En Flujo Normal? | Uso Comun |
static (por defecto) | Sin origen -- top/left se ignoran | Si, sigue el orden del documento | Todo lo que no necesita posicionamiento explicito |
relative | La posicion original del elemento en el flujo | Si, pero desplazado visualmente (espacio original preservado) | Ajustes finos, establecer containing block para hijos |
absolute | Ancestro posicionado mas cercano (cualquier no-static), o el containing block inicial | No -- eliminado del flujo, otros elementos llenan su espacio | Tooltips, insignias, overlays posicionados relativo al padre |
fixed | El viewport (ventana del navegador), a menos que un ancestro tenga transform/filter/perspective | No -- eliminado del flujo | Barras de navegacion fijas, botones flotantes, banners de cookies |
sticky | Ancestro de scroll mas cercano; alterna entre relativo al flujo y fijo al hacer scroll | Si, hasta alcanzar el umbral de scroll | Encabezados de tabla, navegacion de seccion que se pega al scroll |
La tabla anterior es la referencia. Ahora desempaquetemos las tres cosas que causan mas confusion en el mundo real.
Containing Block: El Concepto que la Mayoria de Tutoriales Omite
Si te llevas una cosa de esta pagina, que sea esta: position: absolute no es relativo al elemento padre. Es relativo al ancestro posicionado mas cercano -- el primer ancestro subiendo por el arbol que tiene position con un valor distinto a static.
Esto engania a todo el mundo la primera vez. Pones position: absolute en un hijo, estableces top: 0; left: 0;, y el elemento vuela a la esquina de la pagina en lugar de la esquina del padre. La razon: el padre tiene position: static (por defecto), asi que el navegador sube por el arbol buscando un ancestro posicionado, no encuentra ninguno, y usa el viewport como containing block.
La solucion es siempre la misma: anade position: relative al padre. Esto establece al padre como containing block, y ahora el top/left del hijo se miden desde el borde de padding del padre.
Regla general: si estas usando position: absolute en un hijo, el padre casi seguro necesita position: relative sin desplazamiento. Este patron -- padre relative, hijo absolute -- es el caballo de batalla del posicionamiento de overlays, tooltips e insignias.
Stacking Contexts: El Sistema de Coordenadas del Eje Z
El posicionamiento maneja X e Y. z-index maneja Z -- pero no funciona como la mayoria piensa. z-index solo funciona dentro de un stacking context (contexto de apilamiento), y un stacking context no es lo mismo que el documento.
Piensa en un stacking context como una capa aislada. Los elementos dentro pueden apilarse relativamente entre si, pero la capa entera se posiciona como una unidad contra otras capas. Asi que un z-index: 9999 dentro de un stacking context puede seguir detras de un z-index: 1 en un stacking context diferente de nivel superior.
Que crea un stacking context?
- El elemento raiz (
<html>)
position: absolute o position: relative con z-index distinto de auto
position: fixed o position: sticky (siempre, independientemente del z-index)
- Cualquier elemento con
opacity menor que 1
- Cualquier elemento con
transform, filter, perspective, backdrop-filter o will-change
mix-blend-mode distinto de normal
isolation: isolate
La consecuencia practica: si tu modal con z-index: 9999 aparece detras de un dropdown con z-index: 10, casi seguramente es porque el modal esta dentro de un stacking context (creado por un transform o opacity en un ancestro) que a su vez esta por debajo del stacking context del dropdown. No puedes arreglarlo subiendo el z-index del modal -- necesitas sacar el modal del stacking context del ancestro, o eliminar la propiedad que lo crea.
Unidades de Viewport y Posicionamiento Responsivo
Cuando usas position: fixed o position: absolute con desplazamientos porcentuales, esos porcentajes son relativos a las dimensiones del containing block, no al tamano del propio elemento. top: 50% significa 50% de la altura del containing block.
Las unidades de viewport te permiten posicionar relativo a la ventana real del navegador:
| Unidad | Relativo A | Caso de Uso |
vw / vh | 1% del ancho / alto del viewport | Overlays de pantalla completa, barras laterales fijas |
dvw / dvh | 1% del viewport dinamico (considera la barra de URL movil) | Posicionamiento fijo mobile-first -- evita el salto de la barra de URL de Safari iOS |
svw / svh | 1% del viewport minimo posible | Elementos que nunca deben ocultarse detras de la UI del navegador |
lvw / lvh | 1% del viewport maximo posible | Imagenes de fondo a sangre completa |
Las unidades dvh / dvw / svh / lvh son mas nuevas y resuelven el problema de Safari movil donde 100vh incluye el area detras de la barra de URL, causando que el contenido se corte. Usa 100dvh en lugar de 100vh para elementos que deben llenar el viewport visible.
Patrones del Mundo Real que Funcionan
Overlay de Modal Centrado
El patron para un modal centrado que funciona en todas partes. La clave es combinar position: fixed con inset: 0 (abreviatura de los cuatro lados) y place-items: center:
.modal-backdrop {
position: fixed;
inset: 0; /* top:0; right:0; bottom:0; left:0 */
background: rgba(0,0,0,.5);
display: grid;
place-items: center; /* centers the modal */
z-index: 1000;
}
.modal {
background: #fff;
border-radius: 12px;
padding: 2rem;
max-width: 90vw;
max-height: 90vh;
overflow: auto;
}
Encabezado de Tabla Sticky
position: sticky en un encabezado de tabla es la forma mas limpia de mantener las etiquetas de columna visibles durante el scroll. El truco: top: 0 define cuando se pega, y el contenedor de scroll debe ser el padre de scroll de la tabla:
.table-scroll {
max-height: 400px;
overflow: auto;
}
.table-scroll thead th {
position: sticky;
top: 0;
background: #fff; /* must be opaque */
z-index: 1;
}
Tooltip al Pasar el Mouse
El patron padre relative / hijo absolute para tooltips. El tooltip esta oculto por defecto y se revela al pasar el mouse:
.tooltip-trigger {
position: relative; /* containing block for tooltip */
display: inline-block;
}
.tooltip {
position: absolute;
bottom: 100%; /* sits above the trigger */
left: 50%;
transform: translateX(-50%); /* horizontal center */
margin-bottom: 8px;
padding: .5rem .75rem;
background: #1a1a1a;
color: #fff;
border-radius: 6px;
font-size: .85rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity .2s;
}
.tooltip-trigger:hover .tooltip {
opacity: 1;
}
Marco de Decision: Que position Usar
Deja de memorizar que hace cada valor. Usa este flujo de decision:
El elemento necesita superponer otro contenido o salir del flujo normal?
No → Usa static (por defecto). Usa Flexbox o Grid para el layout.
Si, temporalmente en interaccion (dropdown, tooltip) → relative en el padre, absolute en el hijo.
El elemento debe permanecer visible al hacer scroll?
Si, siempre → fixed (revisa transforms en ancestros!)
Si, pero solo despues de pasar un punto de scroll → sticky.
Solo necesitas empujar un elemento ligeramente de su posicion normal?
Si → relative con pequenos top/left.
FAQ
Por que mi elemento con z-index:9999 aparece detras de uno con z-index:1?
Stacking contexts. Tu elemento con z-index:9999 probablemente esta dentro de un stacking context (creado por transform, opacity o position:fixed en un ancestro) que esta por debajo del stacking context que contiene el elemento con z-index:1. Subir el numero no ayudara. Mueve el elemento fuera del ancestro que crea el stacking context, o elimina esa propiedad.
Por que mi elemento position:fixed se desplaza con la pagina?
Un ancestro tiene transform, filter, perspective, will-change o contain. Estas propiedades crean un containing block para descendientes fixed, haciendolos comportarse como absolute. Encuentra y elimina la propiedad en el ancestro, o mueve el elemento fixed mas arriba en el DOM.
Cual es la diferencia entre position: relative y un desplazamiento de margin?
Ambos mueven el elemento visualmente, pero position: relative preserva el espacio original del elemento en el layout -- otros elementos no se refluyen para llenar el hueco. Un margin desplaza el elemento y causa que los elementos circundantes se refluidan. Usa relative cuando quieras empujar sin afectar a los hermanos.
Deberia usar inset en lugar de top/right/bottom/left?
Si, para codigo moderno. inset: 0 es abreviatura de top:0; right:0; bottom:0; left:0. Tambien puedes hacer inset: 10px 20px para vertical/horizontal. Es mas corto, mas legible y tiene buen soporte (97%+).
position afecta al rendimiento?
position: fixed y position: sticky pueden causar repintados mas frecuentes durante el scroll porque el navegador debe reposicionarlos en cada frame. Anadir will-change: transform puede ayudar al navegador a optimizar, pero el uso excesivo de will-change aumenta el consumo de memoria. Prueba en dispositivos reales si ves tirones de scroll.
Como posiciono algo relativo al centro del viewport?
Usa position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); o el enfoque moderno: position: fixed; inset: 0; margin: auto; width: tu-ancho; height: tu-alto;. Ambos centran el elemento. El enfoque de transform es mas flexible para tamanos desconocidos.