Programação Por Equipe DevBras

Capa do artigo: Margem vertical que não soma no CSS: o colapso de margens explicado com exemplo

Margem vertical que não soma no CSS: o colapso de margens explicado com exemplo

Entenda quando margens verticais se fundem, por que a margem do filho vaza para fora do pai e como controlar o espaço com gap, padding ou flow-root sem gambiarra.

Você coloca margin-bottom: 20px num card e margin-top: 20px no seguinte. Espera 40 pixels entre eles e o inspetor mostra 20. Pior: o título dentro do card tem margem em cima e, em vez de afastar o título da borda do card, empurra o card inteiro para baixo, deixando um buraco acima dele. Parece bug do navegador. É uma regra do CSS com nome: colapso de margens.

A resposta curta: margens verticais de blocos no fluxo normal se fundem, e fica valendo a maior, não a soma. Isso vale entre irmãos e entre pai e primeiro ou último filho, quando nada os separa. Se você quer espaço garantido entre itens, use gap num contêiner flex ou grid. Se quer respiro dentro de um bloco, use padding. Se precisa que o pai contenha as margens do filho, crie um novo contexto de formatação com display: flow-root.

Por que as margens se fundem

A regra vem do mundo dos textos. Pense em parágrafos com margem de uma linha em cima e embaixo. Se as margens somassem, entre dois parágrafos haveria duas linhas de espaço, e no topo de um artigo só uma. O colapso garante espaçamento uniforme: entre dois parágrafos fica a margem de um, não a soma das duas.

A especificação descreve quando duas margens são "adjacentes" e, portanto, se fundem. Os casos que aparecem no dia a dia são três.

Irmãos vizinhos

A margem de baixo de um bloco e a de cima do próximo se fundem. Com 20 e 20, fica 20. Com 30 e 20, fica 30. Se uma for negativa, o resultado é a maior positiva somada à negativa: 30 e -10 dão 20.

Pai e primeiro ou último filho

Se o pai não tem borda nem padding em cima, e nada separa o topo dele do primeiro filho, a margem de cima do filho se funde com a do pai. Na prática, ela "vaza" e aparece fora do pai. É o card que desce em vez do título descer dentro dele. O mesmo acontece embaixo com o último filho, quando o pai tem altura automática.

Bloco vazio

Um bloco sem conteúdo, sem altura, sem borda e sem padding tem a margem de cima fundida com a de baixo. Um <div> vazio usado como espaçador pode não ocupar o espaço que você imaginou.

Quando as margens não colapsam

Saber onde a regra para é tão útil quanto saber onde ela vale. As margens não se fundem:

  • na horizontal, nunca;
  • entre itens de um contêiner flex ou grid;
  • em elementos flutuantes ou com posicionamento absoluto ou fixo;
  • em elementos inline-block;
  • entre um elemento que cria um novo contexto de formatação de bloco e os filhos dele, como acontece com display: flow-root ou overflow diferente de visible;
  • entre pai e filho quando o pai tem borda ou padding naquele lado.

É por isso que o mesmo CSS se comporta diferente quando você troca o contêiner para display: flex. As margens passam a somar, e o espaço que antes era 20 vira 40.

O caso completo: a lista de cards

Vamos usar um exemplo do começo ao fim: uma lista de cards de produto.

<section class="produtos">
  <article class="card">
    <h3>Café torrado 500 g</h3>
    <p>Torra média, moagem para coador.</p>
  </article>
  <article class="card">
    <h3>Filtro de papel 103</h3>
    <p>Caixa com 30 unidades.</p>
  </article>
</section>
.card {
  background: #f4efe6;
  margin: 20px 0;
}

.card h3 {
  margin-top: 16px;
}

Dois sintomas aparecem. Entre os cards, há 20 pixels, não 40. E o margin-top do h3 não afasta o título do topo do card: o card não tem padding nem borda em cima, então a margem do título se funde com a do card e o fundo bege começa colado no texto.

Como controlar o espaço sem brigar com a regra

  1. Abra o inspetor e passe o mouse no elemento. Veja a margem computada, não só a que você escreveu, e observe onde a área colorida da margem aparece.
  2. Para espaço entre itens de uma lista, tire as margens dos itens e use gap no contêiner, com flex ou grid.
  3. Para respiro dentro de um bloco, use padding no bloco, não margem no primeiro filho.
  4. Se precisar manter margens nos filhos e o pai tiver que contê-las, aplique display: flow-root no pai.
  5. Zere a margem do primeiro e do último filho dentro de componentes, para o espaçamento externo ser decidido por quem usa o componente.
.produtos {
  display: grid;
  gap: 20px;
}

.card {
  background: #f4efe6;
  padding: 16px;
}

.card > :first-child {
  margin-top: 0;
}

.card > :last-child {
  margin-bottom: 0;
}

Agora o espaço entre cards é exatamente o gap, 20 pixels, sem depender de colapso. O respiro interno vem do padding, que nunca colapsa. E os seletores de primeiro e último filho evitam que a margem padrão do h3 e do p some ao padding e deixe o card desequilibrado.

Se o layout é um fluxo de texto, como um artigo, o colapso é seu aliado: deixe os parágrafos e títulos com margens e aceite a fusão. A regra só atrapalha quando você monta componentes com caixas e espera matemática de soma. Para alinhar e espaçar componentes sem valores mágicos, o guia de centralizar sem número mágico segue a mesma linha.

Por que não usar o truque do padding de 1 pixel

Uma correção antiga é colocar padding-top: 1px ou uma borda transparente no pai para impedir o vazamento. Funciona, mas esconde a intenção e adiciona um pixel que alguém vai tentar compensar depois. display: flow-root foi criado exatamente para criar um novo contexto de formatação sem efeito colateral visual, e é suportado pelos navegadores atuais. Também é melhor que overflow: hidden, que corta sombras e menus que saem da caixa.

Erros comuns

  • Esperar que margens verticais somem: o layout fica com metade do espaço planejado, e a correção vira número aleatório.
  • Usar margem do primeiro filho como respiro interno: a margem vaza para fora do pai e empurra o componente inteiro.
  • Zerar todas as margens do site num reset agressivo: o texto corrido perde o espaçamento natural e você recria tudo à mão.
  • Corrigir com !important ou valores negativos: o próximo componente quebra de outro jeito e ninguém sabe por quê.
  • Usar overflow: hidden só para conter margens: sombras, tooltips e menus suspensos passam a ser cortados.
  • Trocar o contêiner para flex sem rever margens: o colapso some e o espaço dobra.

Checklist

  • Você conferiu no inspetor a margem computada e onde ela aparece.
  • Listas de itens usam gap em vez de margem em cada item.
  • Respiro interno de componente vem de padding.
  • Primeiro e último filho de componentes não carregam margem para fora.
  • Onde o pai precisa conter margens, há display: flow-root, não um pixel de padding.
  • Nenhuma correção de espaçamento depende de !important.
  • Texto corrido mantém margens e aproveita o colapso.

Selecione no inspetor o bloco que está "com espaço errado" na sua página: a margem de cima dele é a que você escreveu, ou a do primeiro filho que vazou para fora?

Avalie este artigo

Referência: http://forum.imasters.com.br/topic/170421-colocando-margem-em-um-div/