Desmistificando o margin collapsing do CSS
Publicado em 3 ago 2020. Uns 11 minutos de leitura.
Um comportamento que de vez em quando me pega desprevinido de CSS é o margin collapsing (literalmente, "desmoronamento das margens", na falta de uma tradução melhor), que é a combinação de duas margens adjacentes em uma só. É algo que faz parte da especificação mas muitas vezes passa despercebido. Agora, quando isso interfere no que queremos fazer, se você não souber o que é e como funciona, vai quebrar a cabeça para desvendar por que suas margens desaparecem. Trouxe a definição de em quais casos suas margens podem sumir e alguns exemplos para ajudar a visualizar cada um!
Vamos começar definindo o que é margin collapsing: é a junção de duas margens adjacentes de CSS em uma só, resultando apenas na maior das margens existindo. Ou seja, nesta situação, uma das duas margens adjacentes vai desaparecer.
Há três situações em que estas margens adjacentes são criadas. Vamos olhar cada uma delas.
Elementos irmãos adjacentes
Se você tem dois elementos imediatamente adjacentes, ou seja, sem nada entre eles, e ambos têm margens que se encostam (por exemplo, o de cima tem margin-bottom
e o de baixo tem margin-top
), estas margens serão combinadas e uma delas vai se perder (a menor). Vamos ver um exemplo:
<div class="bar"></div>
<div class="bar"></div>
.bar {
margin: 1em 0;
width: 10em;
height: 1em;
background-color: tomato;
}
No exemplo acima, temos duas div
s que têm 1em
de altura. Elas também tem o mesmo tamanho (1em
) de margem para cima e para baixo. Talvez você esperasse que, como o elemento de cima tem uma margem de 1em
para baixo e o elemento de baixo tem uma margem de 1em
para cima, que o espaçamento resultante fosse a soma, ou seja, 2em
. Isso não acontece, e você pode notar que a separação entre os elementos tem o mesmo tamanho que a altura de cada um eles.
O tamanho das margens não precisa ser o mesmo, como foi no exemplo. Caso as margens tenham tamanhos diferentes, a maior vai prevalecer, como podemos ver abaixo:
<div class="bar top"></div>
<div class="bar bottom"></div>
.bar {
width: 10em;
height: 1em;
background-color: forestgreen;
}
.top {
margin-bottom: 1em;
}
.bottom {
margin-top: 2em; /* Esta é maior */
}
Neste caso, a margem resultante foi 2em
(e não 3em
que seria caso fossem somadas). Essa diferença de tamanho é um pouco mais chata de perceber só olhando, mas se você estiver num PC, use o inspetor do navegador para verificar e confirmar o tamanho da margem resultante.
Pai e filho sem separação
Quando um elemento pai tem uma margem que está adjacente a margem de um elemento filho e não há nada entre essas margens, como uma border
ou padding
do elemento pai, haverá também a junção das margens. Por exemplo, se temos um elemento pai com margin-bottom
e seu último filho também tem margin-bottom
, é preciso que haja alguma separação entre as margens para quem as duas existam.
<div class="parent">
<div class="child">A margem deste filho é maior que a do pai e vai substitui-la.</div>
</div>
<div class="outsider">A margem acima é apenas a o filho, de 2em.</div>
.parent {
margin-bottom: 1em;
background-color: lavender;
}
.child {
margin-bottom: 2em;
border: solid 3px indigo;
color: indigo;
}
.outsider {
border: solid 3px purple;
color: purple;
}
No exemplo acima, temos um elemento .parent
que tem uma margin-bottom
de 1em
. Seu filho, .child
, também tem uma margin-bottom
, que neste caso é maior que a do pai (2em
). Como nos outros exemplos, a maior margem vai prevalecer. Mas é interessante notar que a margem maior é aplicada ao parente e não ao descendente, ou seja, ela existe do lado de fora do pai. Por isso que a cor de fundo do elemento pai não se estende abaixo do filho.
Se separássemos as margens do pai e do filho, por exemplo, colocando uma borda no elemento pai, essas margens não se juntariam mais. Vamos testar modificando o exemplo anterior:
<div class="parent">
<div class="child">A margem deste filho é maior que a do pai mas agora elas estão separadas.</div>
</div>
<div class="outsider">Existem duas margens acima, de 2em no filho e 1em no pai.</div>
.parent {
margin-bottom: 1em;
border: solid 3px orange; /* Isso separa as margens */
background-color: lemonchiffon;
}
.child {
margin-bottom: 2em;
border: solid 3px peru;
color: peru;
}
.outsider {
border: solid 3px sandybrown;
color: sandybrown;
}
Outros itens que separem as duas margens também impediriam o margin collapsing de acontecer, como um padding
no elemento pai, um elemento inline (como um texto) após o elemento filho (mas ainda dentro do pai), uma altura (height
ou min-height
) no elemento pai que separe as duas margens, e por aí vai.
Um bloco sem conteúdo
Um elemento que esteja vazio, ou seja, que não tenha padding
, border
, altura ou conteúdo inline como texto, caso tenha margens, estas estarão adjacentes, pois não haverá nada para separar a margin-top
da margin-bottom
do elemento. Neste caso, o margin collapsing vai acontecer. Vamos de exemplo:
<div class="bar"></div>
<div class="empty"></div>
<div class="bar"></div>
.bar {
width: 10em;
height: 1em;
background-color: steelblue;
}
.empty {
margin: 1em 0;
}
Neste caso, o elemento .empty
possui margem superior e inferior de 1em
cada. Porém, como ele não tem nenhum conteúdo, essas duas margens se encostam e viram uma só com o tamanho da maior (como são iguais, a margem resultante é 1em
). O resultado é bem similar ao primeiro exemplo, mas dessa vez o margin collapsing aconteceu entre duas margens do mesmo elemento, ao invés de elementos irmãos.
Assim como no exemplo antes deste, se o elemento vazio separar as margens de alguma maneira (e deixar de ser vazio, no caso), as duas passarão a existir separadamente:
<div class="bar"></div>
<div class="not-empty"></div>
<div class="bar"></div>
.bar {
width: 10em;
height: 1em;
background-color: crimson;
}
.not-empty {
margin: 1em 0;
height: 1px; /* Isso separa as margens */
background-color: pink;
}
A mudança no exemplo acima foi apenas a adição de uma altura, neste caso de apenas 1px
. Isso é suficiente para separar as duas margens e impedir o margin collapsing. Eu coloquei uma background-color
apenas para facilitar a visualização das duas margens separadas, mas ela não faz diferença no margin collapsing. Mesmo sem a cor, as bordas estariam separadas por conta da altura. Um texto dentro do elemento seria também evitaria que as bordas se juntassem.
Alguns detalhes a mais
Nos exemplos deste post, vimos que duas margens imediatamente adjacentes vão ser juntadas em uma só e o tamanho da margem resultante é o maior tamanho entre elas. Mas este efeito não é limitado a duas margens apenas. Podemos, por exemplo, combinar todos os casos acima para mostrar várias margens que vão ser juntas em uma só:
<div class="bar"></div>
<div class="empty"></div>
<div class="empty"></div>
<div class="empty"></div>
<div class="empty">
<div class="empty"></div>
<div class="empty"></div>
</div>
<div class="empty"></div>
<div class="bar"></div>
.bar {
width: 10em;
height: 1em;
background-color: limegreen;
}
.empty {
margin: 1em 0;
}
Pra finalizar, vale mencionar que as margens afetadas por este efeito nem sempre serão positivas. Por exemplo, podemos ter um elemento filho com margem positiva e um elemento pai com margem zero: o margin collapsing vai colocar a margem positiva no pai caso o filho seja o último elemento do pai e não haja nada separando a margem dele do ponto em que a margem (originalmente zero) do pai deveria estar.
<div class="parent">
<div class="child">Este filho tem margem, mas ela vai ser aplicada no pai.</div>
</div>
<div class="outsider">A margem do filho foi aplicada acima.</div>
.parent {
margin-bottom: 0; /* Zero */
background-color: lavenderblush;
}
.child {
margin-bottom: 2em;
border: solid 3px mediumvioletred;
color: mediumvioletred;
}
.outsider {
border: solid 3px orchid;
color: orchid;
}
Se você usar o inspetor no exemplo acima, vai ver que a altura do pai não aumentou para acomodar a margem do filho (e, por isso, a cor de fundo do pai não se estende além da altura do filho).
E quanto a margens negativas? Elas existem, e são confusas ao ponto de merecer uma discussão separada só sobre elas. Mas, especificamente no caso de margin collapsing, há duas possibilidades.
Se houver margens positivas e negativas, a margem resultante e a soma da maior margem com a menor margem (a "mais positiva" + a "mais negativa"). No exemplo abaixo, temos como margem resultante entre os elementos 1em + (-2em) = -1em
.
<div class="bar top"></div>
<div class="bar bottom"></div>
.bar {
height: 3em;
}
.top {
width: 10em;
margin-bottom: 1em; /* Positiva */
background-color: lightseagreen;
}
.bottom {
width: 5em;
margin-top: -2em; /* Negativa */
background-color: lightgreen;
}
Se houver apenas margens negativas, a margem resultante é a menor margem (a "mais negativa" entre elas). No próximo exemplo, a menor (mais negativa) entre -1em
e -2em
vai prevalecer, tendo como margem resultante -2em
.
<div class="bar top"></div>
<div class="bar bottom"></div>
.bar {
height: 3em;
}
.top {
width: 10em;
margin-bottom: -1em; /* Negativa */
background-color: olivedrab;
}
.bottom {
width: 5em;
margin-top: -2em; /* Negativa */
background-color: yellowgreen;
}
Quando você não sabe que margin collapsing existe, ela te pega de surpresa e você pode passar horas para tentar entender pra onde suas margens estão desaparecendo. Mas não tem muito mistério quando você entende o funcionamento! Espero que os exemplos deste post tenham sido mais esclarecedores do que confusos 😅. Se quiser ler mais, a documentação de margin collapsing na MDN fala destes casos e tem links para algumas definições mais específicas do que pode ou não separar duas margens.
Topa um exercício?
Aqui vão alguns exemplos de código para você praticar o que vimos acima, se quiser.
Qual é o espaçamento entre as barras?
<div class="bar"></div>
<div class="bar"></div>
.bar {
margin: 1em 0;
width: 10em;
height: 1em;
background-color: tomato;
}
O que causaria um espaçamento de pelo menos 2em
entre as barras?
<div class="bar"></div>
<div class="empty"></div>
<div class="bar"></div>
.bar {
width: 10em;
height: 1em;
background-color: steelblue;
}
.empty {
margin: 1em 0;
}
Qual é o resultado deste código?
<div class="bar top"></div>
<div class="bar bottom"></div>
.bar {
height: 3em;
}
.top {
width: 10em;
margin-bottom: -1em;
background-color: olivedrab; /* Verde-escuro */
}
.bottom {
width: 5em;
margin-top: -3em;
background-color: yellowgreen; /* Verde-claro */
}
Gostou? Que tal compartilhar?