soy Kseso y esto EsCSS

Margin Collapsed: 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: Márgenes desaparecidos o que empujan ancestros

xPor Kseso ✎ 9
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.

Evitando el cierre de márgenes (margin collapsed)

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

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

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.

Otras posibles soluciones para evitar el colapso de los márgenes pasan no por impedir que se cierren, sino para que no aplique esta 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.

Artículo publicado originalmente el 24/02/1013. Actualizado el 16 de Febrero de 2015.

avatar del Editor del blog

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 Don Kseso Kseso

Comentarios: 9

  1. En el último ejemplo de donde sale -10px? No será -5px?

    ResponderEliminar
  2. De un lapsus mio, George. Bien visto.
    Gracias.

    ResponderEliminar
  3. Muchas gracias compañero, estoy iniciando en esto y no entendía porque pasaba esto.
    Gracias.

    ResponderEliminar
  4. Excelente info, yo también me considero de los que ignoraban Margin collapsed.
    Muchas gracias por compartir.

    ResponderEliminar
  5. Un detalle sobre el agregado de '1px' ... si se usa '.01px' se "colapsa" igual, y en la práctica es '0px'. Es un truco para luego no compensar el pixel entero en otro valor.

    ResponderEliminar
    Respuestas
    1. ¡Aja! la vieja técnica de achicar el píxel, Furoya
      Veo que sigue funcionando, aunque creo recordar que tradicionalmente tenía el peligro de las inconsistencias entre navegadores (y versiones) al manejar valores no enteros en píxeles.

      Un saludo

      Eliminar
    2. Tal cual. Hasta hace pocos años algunos navegadores ignoraban el subpixel, así que ponerlos con decimales era lo mismo que nada. Ahora la cosa ha cambiado bastante, por eso me pareció que se justificaba el comentario, aprovechando tu actualización del artículo.
      Gracias, y un saludo para vos también.

      Eliminar
  6. Porque colapsa el margen derecho si intento poner en body un width:100% y un margin de 20px?

    ResponderEliminar
    Respuestas
    1. Códigos, Pablo, códigos por favor.

      Ya se ha demostrado que como adivino no tengo futuro 😎

      Un saludo

      Eliminar

EsCss RSS del Blog RSSS Comentarios Humans.txt ᛯ Diseno por Kseso SiteMap