soy Kseso y esto EsCSS

Margin Collapsed revisado: Márgenes desaparecidos o que empujan ancestros

El problema de los márgenes que colapsan (margin collapsed) empujando a su padre hacia abajo o que entre hermanos desaparece uno de ellos quedando más juntos que lo deseado. A fondo y con varias solucciones.

Margin Collapsed revisado: Márgenes desaparecidos o que empujan ancestros

·Por Kseso ✎ 13

Artículo publicado originalmente el 24/02/1013. Actualizado en Febrero de 2015, Febrero de 2017 y Febrero de 2018

Margin Collapsed: Márgenes desaparecidos o que empujan a los ancestros
Collapsed margins: Márgenes que empujan al padre y no al elemento que lo tiene declarado
Collapsed margins

¿Te ha pasado? Tienes un contenedor principal y dentro tu h1 con un margen superior. Vas al navegador y te encuentras que el background del body deja un hueco en la parte superior. Descubres que el margen del h1 en vez de afectarle a él separándolo de su contenedor afecta a un abuelo suyo (el body).

Esta es una situación que suele desquiciar un poco a quien se está iniciando en Css. Porque por más que pones todos los márgenes habidos y por haber a cero, el espacio continúa ahí.

Por si has tenido la suerte de no saber de qué hablo, mira el ejemplo siguiente

See the Pen bNLzdo by Kseso (@Kseso) on CodePen.

Ver Demo

Si te fijas en el código verás que el único elemento que tiene declarado un margen es el h1. Todos los demás están puestos a cero: * {margin: 0;}

Parece magia negra u brujería. En el ejemplo, el margen del h1 se lo queda no su padre (el div que lo contiene) sino su abuelo, el body que se separa esos px de la parte superior de la ventana y deja ver el fondo del html (la franja superior roja).

Además hay otro efecto añadido. Al haber declarado al body una altura del 100%, el margen del h1 fuerza la aparición del scroll vertical.

El porqué sucede: Collapsed margins

Dice la especificación sobre el comportamiento de los márgenes:

En CSS los márgenes adyacentes (ningún padding o border que los separe) de dos o más cajas (que pueden estar una al lado de la otra o anidadas) se combinan para formar un solo margen.
Los márgenes laterales (horizontales) no se combinan o cierran entre ellos, sólo los superiores e inferiores (verticales).

Esto es lo que se conoce como márgenes cerrados o collapsed margins. Y ya que estamos con lo que dice la especificación, un poco más de ella para terminar de comprender este comportamiento:

  1. Dos o más márgenes verticales adyacentes de cajas de bloques en el flujo normal se cierran. El ancho del margen resultante es el máximo de los anchos de los márgenes adyacentes. En el caso de márgenes negativos, el máximo absoluto de los márgenes adyacentes negativos es restado del máximo de los márgenes adyacentes positivos. Si no hay ningún margen positivo, el máximo absoluto de los márgenes adyacentes negativos es restado de cero.
  2. Los márgenes verticales entre una caja flotante y cualquier otra caja no se cierran.
  3. Los márgenes de cajas con posiciones absoluta y relativa no se cierran.

Tengo una mala noticia para ti, ahí fuera no hay ninguna propiedad que controle el "collapsed margins" a voluntad a diferencia de los bordes adyacentes que sí se controlan: border-collapse

Así que con lo anterior ya sabes la raíz del problema o qué está sucediendo. Y lo más importante, te ahorras fatigas y minutos de intentos vanos para hacer desaparecer un margen que no existe (el del body del ejemplo) y que se manifieste el que sí está declarado en el Css (el del h1).

Pero no preocuparse, pese a que este cierre de márgenes se hace de forma automática y que no haya algo como margin-collapse: none al estilo de los bordes en las tablas, sí hay soluciones. Y varias posibles.

El cierre de márgenes (margin collapsed) y tipo de display

Antes de pasar a ver las formas de evitar que los márgenes colapsen hay que tener presente que es un efecto dependiente del tipo de display usado.

Por lo tanto el efecto del margin collapse no se producirá:

  1. Entre elementos con display: table-cell o display: table-row
  2. En el espacio del display: grid los márgenes de sus ítems no colapsan.
  3. En el espacio del display: flex los márgenes de sus ítems no colapsan.

Evitando el cierre de márgenes (margin collapsed)

Si no estás en las situaciones anteriores (tipo de display) y quieres evitar el cierre de márgenes, puedes optar por alguna de las siguientes solucciones.

Básicamente el colapso de los márgenes se evita si entre los márgenes que se cierran se intrpone algún tipo de padding, border, o elemento del tipo "inline" (y un espacio en blanco textual   -no de barra espaciadora- también lo es.

Pero recuerda que cada una de las respuestas al cierre de los márgenes tendrá sus efectos no deseados en según qué casos. Así que si te topas con el collapsed margins debes evaluar cuál se adapta mejor en cada situación.

Actuando sobre el padre

Si vuelves a la primera cita de este artículo de las especificaciones, verás que una condición es que no haya paddings o bordes interpuestos entre los márgenes. Así que sólo es necesario añadir un borde entre el margen del h1 y el borde cerrado. Esto es, al div que contiene al h1. Arreglado como ves en la captura de abajo:

Cierre de márgenes (margin collapse) anulado con border
Cierre de márgenes (margin collapse) anulado con border

Ver Demo

El mismo resultado se obtiene si en vez de declarar un borde se declara un padding.

Ahora el "desplazamiento" que hemos anulado en el body se ha trasladado al interior del div, separando el h1 del borde superior según el valor declarado para su margen superior y el scroll vertical ya no aparece.

Actuando sobre el abuelo

Como en el ejemplo hay implicados tres elementos de forma premeditada (el h1 que tiene el margen, su padre el div y su abuelo el body), también podemos actual sobre el abuelo en vez del padre de la misma forma, vía border o padding.
Pero con un resultado ligéramente distinto:

Cierre de márgenes (margin collapse) anulado con padding en el abuelo
Cierre de márgenes (margin collapse) anulado con padding en el abuelo

En este caso, el abuelo (el body) queda libre del cierre de los márgenes, no así el div. Por lo tanto desaparece el espacio superior del body pero es el div el que pasa a desplazarse por el margen del h1.

Evita el desborde del margen: overflow

Una tercera vía de soluccionar el cierre de los márgenes es evitar que haya desbordamientos en el padre (el div). Sólo es necesario declarar overflow con un valor distinto de visible (que es el "valor por defecto").

Cierre de márgenes (margin collapse) anulado con overflow
Cierre de márgenes (margin collapse) anulado con overflow

Elementos flotados y posicionados

Otras posibles soluciones para evitar el colapso de los márgenes pasan no por impedir que se cierren, sino para que no aplique este efecto.

Si vuelves a la segunda cita de las especificaciones, punto 1 y 2, verás que el cierre o colapso de los márgenes no se produce cuando la caja que lo tiene declarado está flotada o posicionada como absolute (y fixed es un tipo de absolute). Lógico. Salen del flujo.

Márgenes colapsados en elementos anidados

Un caso particular se da cuando tenemos varios elementos anidados, uno dentro de otro, dentro del tercero..., como el siguiente html:

<div class="caja a"> <div class="caja b"> <div class="caja c"> <div class="caja d"> <div class="caja e"> Los márgenes superiores e inferiores colapsan, los laterales no. </div> </div> </div> </div> </div>

Con el siguiente Css declarado:

.caja {margin: 10px;} .a {background: #444;} .b {background: #777;} .c {background: #aaa;} .d {background: #ccc;} .e {background: #eee;}
Margin collapse cajas anidadas
Margin collapse cajas anidadas

El resultado obtenido por efecto del cierre de los bordes es "sorprendente" si no cuentas con él. Es el de la imagen superior de la derecha. Sólo tenemos las franjas grises en los laterales de los elementos, no en la parte superior e inferior de los divs.

Sin margin collapse cajas anidadas
No margin collapse cajas anidadas

En esta situación se corrige con cualquiera de los métodos mencionados antes. Basta añadir un padding a los divs para que aparezcan todos los tonos de grises en los cuatro laterales de todas las cajas anidadas. Y si el añadir este px extra .caja {padding: 1px;} supone un quebranto siempre puedes recurrir o a restarlo del margen o usar la variante del modelo de caja box-sizing.

Colapso de márgenes entre hermanos

Cierre de márgenes (margin collapse) entre hermanos
Margin collapse entre hermanos

En el caso anterior los elementos implicados estaban anidados, unos dentro de otros. Por eso las solucciones de interponer en un ancestro un borde, padding o evitar debordamientos, repito, en el ancestro, eran efectivas. Pero hay otro caso de cierre de márgenes o collapsed margins en que estas acciones no son válidas.

De nuevo a las especificaciones. Primera cita. Casos en que se produce el colapso: elementos anidados o adyacentes. Esto es, hermanos. Como la imagen de la derecha o este jsfiddle

Como ves, pese a tener un margen superior e inferior de 20px, entre los hermanos sólo hay una separación de 20px, no de 40.

En estas situaciones basta con intercalar en el html entre los hermanos cualquier elemento. Un simple espacio en blanco &nbsp;, no de teclado sino como entidad html, surte efecto y la separación vertical entre hermanos es la suma de sus márgenes. Puedes verlo en el siguiente pen, entre los dos últimos div´s hay un &nbsp; intercalado:

See the Pen LEQqzG by Kseso (@Kseso) on CodePen.

Ver Demo

Márgenes positivos y negativos

Un caso particular de cierre de márgenes que colapsan se dan cuando uno de los valores es negativo. Tengamos el caso de dos hermanos:
.uno {margin-bottom: 20px;}
.dos {margin-top: -10px;}
En este caso la distancia que los separa es la suma de sus márgenes: 20px+(-10px)=10px

Si ocurriese que el resultado fuese un valor negativo el segundo elemento se dibujará tapando una parte del primero.

avatar del Editor del blog

the obCSServer ᛯ Ramajero Argonauta, Enredique Amanuense de CSS.
#impoCSSible inside
Dicen que, en español, EsCss es el mejor blog de CSS. Posíblemente exageren.
@Kseso EsCss Kseso