La posición estática hipotética y el hueco que deja relative
Dónde se coloca un absoluto que no declara desplazamientos, por qué relative deja un agujero donde estaba, y los patrones que explotan las dos cosas.
Dos comportamientos del posicionamiento se explican con el mismo concepto y casi nunca se enseñan juntos: un absoluto sin desplazamientos no se va a la esquina, se queda donde habría estado; y un relative desplazado deja intacto el hueco que ocupaba. Los dos hablan de una posición que el motor calcula aunque no la use directamente, y saber que existe convierte dos rarezas en dos herramientas.
- Definir la posición estática hipotética de un elemento fuera de flujo.
- Predecir dónde cae un absoluto que declara solo uno de los dos ejes.
- Explicar por qué
relativeno recoloca a sus hermanos. - Usar los dos comportamientos en patrones concretos.
Un absoluto sin inset no se va a ningún sitio
Prueba mental: un div en medio de un párrafo, con position: absolute y sin ningún desplazamiento declarado. La intuición mayoritaria dice que se irá a la esquina superior izquierda de su bloque contenedor. No pasa eso. Se queda exactamente donde estaba.
La razón es que el valor inicial de las cuatro propiedades de desplazamiento es auto, y auto en un elemento fuera de flujo significa: colócalo en su posición estática. La posición estática es la que habría tenido el elemento si su position fuera static, calculada con las mismas reglas del flujo normal y teniendo en cuenta a sus hermanos anteriores.
/* se queda donde el flujo lo habría puesto, pero sin ocupar sitio */
.marca { position: absolute; }
Esto es más útil de lo que parece. Un patrón habitual: una insignia que debe aparecer junto a un texto de longitud variable sin empujarlo. Si declaras solo el eje que necesitas mover, el otro conserva la posición estática.
.contenedor { position: relative; }
.insignia { position: absolute; inset-inline-start: 100%; }
/* el eje de bloque no se declara: cae a la altura donde estaba en el flujo */
El comportamiento por ejes es independiente. Puedes declarar inset-block-start y dejar el eje en línea en posición estática, o al revés. Es la base de muchos anclajes sencillos que no necesitan ni cálculo ni JavaScript.
Un matiz importante: la posición estática de un absoluto depende del contenido que lo rodea, y se recalcula cuando ese contenido cambia. Si el párrafo anterior crece una línea, tu absoluto sin desplazamientos baja con él. Eso puede ser exactamente lo que quieres o una fuente de inestabilidad, según el caso; lo que no puede es sorprenderte.
El hueco que deja relative
El caso simétrico. Un elemento con position: relative e inset-block-start: 40px se dibuja cuarenta píxeles más abajo, pero el flujo sigue reservando su hueco original. Los hermanos no se recolocan. Donde el elemento se ha ido queda un agujero, y donde ha llegado se solapa con lo que hubiera.
Esto no es un defecto: es la razón de ser de relative. La propiedad se diseñó para desplazar la pintura sin perturbar el layout, precisamente porque perturbar el layout es caro y propaga cambios a toda la página. Un relative es una operación local que no obliga a recalcular nada de alrededor.
flowchart TB
A[El motor calcula el flujo normal] --> B[Cada elemento obtiene su posicion estatica]
B --> C{Que valor de position tiene}
C -->|static| D[Se pinta en su posicion estatica]
C -->|relative| E[Reserva el hueco de la posicion estatica]
E --> F[Se pinta desplazado segun inset]
C -->|absolute o fixed| G[No reserva hueco]
G --> H{Declara desplazamientos}
H -->|Si| I[Se coloca contra el bloque contenedor]
H -->|No| J[Se coloca en su posicion estatica hipotetica]
style A fill:#89b4fa,color:#11111b
style D fill:#a6e3a1,color:#11111b
style F fill:#f9e2af,color:#11111b
style I fill:#a6e3a1,color:#11111b
style J fill:#cba6f7,color:#11111bDe aquí sale una regla operativa para animaciones: nunca animes inset si puedes animar translate. Los dos desplazan visualmente un relative, pero inset obliga al motor a rehacer el layout en cada fotograma y translate se resuelve en composición. El resultado visual es idéntico y el coste no.
/* caro: layout en cada fotograma */
.mal { position: relative; transition: inset-block-start .3s; }
.mal:hover { inset-block-start: -4px; }
/* barato: solo composición */
.bien { transition: translate .3s; }
.bien:hover { translate: 0 -4px; }
Hay un caso donde relative con desplazamientos sigue siendo la respuesta correcta: cuando el desplazamiento debe ser estático y estructural, no animado, y quieres que quede reflejado en el valor computado del layout para que otros cálculos lo vean. Y hay otro donde no debes usar ninguno de los dos: si lo que quieres es que el hueco también se mueva, lo que necesitas es un margen, no un posicionamiento.
Los patrones que explotan las dos cosas
El elemento que ocupa sitio pero se ve fuera. Un icono decorativo que debe sobresalir del borde de una tarjeta sin recortarse ni ampliarla. position: relative con un desplazamiento negativo consigue justo eso: el hueco sigue dentro, el dibujo sale fuera.
El ancla implícita. Un relative sin desplazamientos es el patrón más usado de todo el CSS y no desplaza nada: existe solo para ser el bloque contenedor de un descendiente absoluto. Es gratuito en rendimiento y no crea contexto de apilamiento mientras no le pongas z-index.
La superposición sin medir. Combinando las dos ideas: un contenedor relative y un hijo absolute con inset: 0 produce una capa exactamente del tamaño del contenedor, incluyendo su padding, sin conocer ninguna medida. Es la base de velos, overlays de carga y capas de degradado sobre imágenes.
.tarjeta { position: relative; }
.tarjeta .velo {
position: absolute;
inset: 0;
background: linear-gradient(to top, oklch(0% 0 0 / .7), transparent 60%);
pointer-events: none;
}
Ese pointer-events: none del final es la pieza que casi todo el mundo olvida: una capa absoluta que cubre la tarjeta entera intercepta los clics de todo lo que hay debajo. Si la capa es decorativa, tiene que ser transparente al puntero.
Aquí hay una zona gris de la especificación que conviene conocer antes de apoyarse en ella. Calcular la posición estática de un elemento fuera de flujo obliga al motor a responder una pregunta contrafáctica: dónde habría estado esta caja si hubiera participado en el flujo. Para un bloque dentro de otro bloque la respuesta es sencilla. Pero para un absoluto que es hijo de un contenedor flex o grid, la pregunta se complica: ¿su posición estática es la que le habría dado el algoritmo de flex, con su alineación y su reparto de espacio, o simplemente la esquina de inicio del contenedor? La especificación de flexbox dice que la posición estática de un hijo absoluto se determina como si fuera el único elemento flexible, respetando align-self y justify-content del contenedor, y la de grid dice que si el absoluto tiene un área de rejilla declarada, su bloque contenedor es esa área. Las dos reglas son razonables y las dos llegaron después de que los motores ya hubieran implementado algo, con el resultado de que en este rincón concreto ha habido divergencias reales entre navegadores durante años. La lección práctica no es evitar el patrón, que hoy funciona bien y es muy útil —un absoluto en una celda de rejilla se puede centrar con place-self sin declarar un solo desplazamiento—, sino probarlo en los tres motores antes de apoyar un layout crítico en él, y desconfiar cuando el resultado dependa de la interacción entre alineación y posición estática.
- Pon
position: absolutea unspanen medio de un párrafo, sin desplazamientos, y comprueba que no se mueve. - Alarga el texto anterior y observa cómo la posición estática se recalcula.
- Declara solo
inset-inline-start: 100%y verifica que el eje de bloque conserva la posición estática. - Compara el coste en el panel de rendimiento de animar
inset-block-startfrente atranslate. - Coloca un absoluto dentro de una celda de rejilla y céntralo con
place-self: centersin declarar desplazamientos.