Qué impide el colapso de márgenes
Las dos formas de cortarlo, cuáles son los elementos cuyos márgenes nunca colapsan, y por qué display flow-root existe precisamente porque llevábamos quince años abusando de overflow hidden.
Hay dos maneras de impedir que dos márgenes colapsen, y solo dos: interponer algo entre las aristas de las que hablan, o hacer que esas aristas dejen de pertenecer al mismo contexto de flujo. Todas las recetas que has visto por ahí —el borde transparente, el relleno de un píxel, el overflow: hidden, el display: flow-root— son una de las dos, y se diferencian únicamente en los efectos colaterales que arrastran.
- Enumerar qué basta con interponer para cortar el colapso en cada arista.
- Distinguir interponer algo de establecer un contexto de formato independiente.
- Saber qué elementos tienen márgenes que no colapsan nunca.
- Elegir la solución por sus efectos colaterales y no por costumbre.
Lo que basta con interponer
Recuerda la formulación: dos márgenes colapsan cuando hablan de la misma arista. Si aparece cualquier cosa entre ellas, ya son dos aristas distintas y no hay nada que fundir.
Para la arista superior, entre un padre y su primer hijo, basta con uno de estos:
/* Un borde, aunque sea invisible. */
.padre { border-block-start: 1px solid transparent; }
/* Un relleno, aunque sea de un pixel. */
.padre { padding-block-start: 1px; }
/* O contenido en linea antes del hijo, que genera una caja de linea. */
Para la arista inferior, entre un padre y su último hijo, valen los equivalentes de abajo y además uno más:
/* Una altura que no sea automatica corta el colapso inferior. */
.padre { min-block-size: 1px; }
El motivo de esa asimetría es que el colapso inferior solo ocurre cuando la altura del padre la determina su contenido. En cuanto declaras una altura o una altura mínima, el borde inferior del padre deja de estar pegado al del hijo y hay algo que separar.
Estas soluciones son legítimas cuando el borde o el relleno los querías de todas formas. Usadas solo para cortar el colapso son un truco: alteran la geometría de la caja un píxel, y ese píxel aparece antes o después en un cálculo ajeno.
Lo que lo corta de raíz
La otra vía es que el padre establezca un contexto de formato de bloque independiente. Cuando lo hace, sus márgenes no colapsan nunca con los de sus hijos, con o sin borde, con o sin relleno.
/* La forma explicita, creada exactamente para esto. */
.padre { display: flow-root; }
Hay otras muchas cosas que también lo establecen, y ahí está el matiz que importa: casi todas lo hacen como efecto secundario de otra función.
/* Efecto secundario de crear un contenedor de desplazamiento. */
.padre { overflow: hidden; }
/* Efecto secundario de sacar el elemento del flujo. */
.padre { position: absolute; }
.padre { float: left; }
/* Efecto secundario de cambiar el modelo de layout. */
.padre { display: flex; }
.padre { display: grid; }
.padre { display: inline-block; }
/* Efecto secundario de aislar el layout por rendimiento. */
.padre { contain: layout; }
El catálogo completo de lo que crea un contexto de formato, con el detalle de qué garantiza cada uno, está en el catálogo del BFC. Para lo que nos ocupa basta con la consecuencia: todos cortan el colapso, y cada uno trae su propio equipaje.
Los que nunca colapsan
Hay elementos cuyos márgenes están fuera del juego desde el principio.
Los flotantes. Un elemento flotado no colapsa márgenes con nada, ni con sus hermanos ni con su padre.
Los posicionados de forma absoluta. Están fuera del flujo normal, y el colapso es un fenómeno del flujo normal.
Los elementos flex y los elementos grid. Sus márgenes no colapsan entre sí ni con los del contenedor. Ésta es la razón de fondo por la que los layouts posteriores a 2017 apenas tropiezan con el asunto: en cuanto el contenedor es flex o grid, el colapso deja de existir dentro de él.
El elemento raíz. html no colapsa sus márgenes con los de nadie.
Qué elegir y por qué existe flow-root
La regla de decisión es corta.
Si el borde o el relleno los querías igualmente, ya está resuelto y no hace falta nada más.
Si el contenedor va a ser flex o grid de todas formas, tampoco: el problema desaparece solo.
Si lo único que quieres es contener, usa display: flow-root. No recorta nada, no crea un contenedor de desplazamiento, no cambia el modelo de layout de los hijos y no rompe nada que estuviera funcionando.
Nunca uses overflow: hidden para esto. Es la receta más repetida de internet y la que más daño hace, porque arrastra tres efectos que no querías: recorta sombras, contornos y cualquier cosa que sobresalga; convierte el elemento en contenedor de desplazamiento, lo que desactiva position: sticky para sus descendientes; y esconde desbordamientos reales que preferirías ver.
El fallo típico no aparece el día que escribes overflow: hidden. Aparece meses después, cuando alguien añade una cabecera pegajosa dentro de ese contenedor y no se pega, o cuando un menú desplegable se corta por el borde. Nadie relaciona el síntoma con una línea escrita para arreglar un margen, porque no hay ninguna relación aparente entre las dos cosas.
display: flow-root es una de las adiciones más reveladoras que ha tenido CSS, porque no aporta ninguna capacidad nueva: hace exactamente lo que ya hacía overflow: hidden, y se añadió únicamente porque llevábamos quince años usando overflow: hidden para algo que no era su función. Ese patrón se repite en toda la historia del lenguaje y merece la pena tenerlo identificado, porque te permite reconocerlo mientras ocurre en vez de veinte años después. El clearfix usaba un pseudo-elemento con clear para contener flotantes, y lo que se quería era contener; llegó flow-root y lo hizo honesto. transform: translateZ(0) se usaba para forzar una capa de composición, y lo que se quería era una pista de rendimiento; llegó will-change y lo hizo honesto. position: relative sin desplazamiento se usaba para crear un contexto de apilamiento, y lo que se quería era aislar el z-index; llegó isolation: isolate y lo hizo honesto. En los cuatro casos, la solución vieja funcionaba y te hacía dueño de todos los demás efectos de esa propiedad: el clearfix añadía un pseudo-elemento que estorbaba, el translateZ gastaba memoria de vídeo en cada elemento, el position: relative cambiaba el bloque contenedor de los descendientes absolutos, y el overflow: hidden mata el sticky. Ninguno de esos efectos era visible el día que escribiste la línea, y todos aparecieron después, en un cambio que no tenía nada que ver. La regla general que sale de aquí vale para cualquier lenguaje pero en CSS es especialmente rentable: cuando uses una propiedad por algo distinto de su propósito declarado, escribe al lado por qué la has puesto, y revisa periódicamente si la plataforma ya ha añadido la versión honesta. Casi siempre la ha añadido, casi siempre nadie se ha enterado, y el cambio es de una línea.
- Reproduce el colapso padre-hijo y córtalo con un relleno de un píxel.
- Córtalo después con
min-block-sizey comprueba que solo funciona en la arista inferior. - Córtalo con
display: flow-rooty verifica que la geometría no cambia. - Córtalo con
overflow: hiddeny añade dentro un elementostickypara ver el efecto colateral. - Busca en tu proyecto los
overflow: hiddenescritos para contener y sustitúyelos.