margin: внешние отступы и схлопывание (margin collapse) — как его избежать (CSS)

Схлопывание margin (margin collapse) — это когда вертикальные отступы двух соседних блоков не складываются, а «схлопываются» в один — берётся больший из двух. Это не баг, а часть спецификации CSS, но именно из‑за него часто «ломается» верстка: отступы получаются меньше ожидаемых, элементы прилипают друг к другу, а при масштабировании всё едет.


Где вообще бывает схлопывание

Оно работает только для вертикальных margin (top/bottom) и только между:

  • соседними родственными элементами (siblings),
  • родителем и первым/последним дочерним элементом,
  • пустыми блоками (у которых нет ни контента, ни padding, ни border).

Горизонтальные margin никогда не схлопываются — они всегда суммируются.

Пример 1: соседи

html
<div class="box">Блок 1</div>
<div class="box">Блок 2</div>
css
.box {
  margin-bottom: 20px;
}

Между блоками будет 20 px, а не 40 px. Браузер берёт больший из двух смежных отступов.


Пример 2: родитель и первый ребёнок

html
<div class="container">
  <div class="item">Элемент</div>
</div>
css
.container {
  margin-top: 50px;
}
.item {
  margin-top: 30px;
}

Результат: отступ сверху у контейнера будет 50 px (больший из двух), а не 80 px. Margin «вытекает» наружу.


Как гарантированно избежать схлопывания

Есть несколько рабочих способов. Выбирай по задаче.

Способ 1: использовать border или padding у родителя

Любое значение border или padding (даже 1 px) отключает схлопывание между родителем и ребёнком.

css
.container {
  padding-top: 1px; /* или border-top: 1px solid transparent; */
}
.item {
  margin-top: 30px;
}

Теперь margin ребёнка не будет схлопываться с отступом родителя. Минус: появляется лишний 1 px, но это почти незаметно, а логика становится предсказуемой.


Способ 2: создать BFC (Block Formatting Context)

Самый правильный способ: заставить контейнер создать свой контекст форматирования. Тогда его margin не будет схлопываться с дочерними.

Рабочие варианты:

css
.container {
  overflow: auto; /* самый частый вариант */
  /* или */
  display: flow-root; /* современный и чистый вариант */
}

Пример:

html
<div class="container">
  <div class="item">Элемент</div>
</div>
css
.container {
  margin-top: 50px;
  overflow: auto; /* отключает схлопывание */
}
.item {
  margin-top: 30px;
}

Теперь сверху будет 80 px: 50 px контейнера + 30 px элемента.

display: flow-root делает то же самое, но без побочных эффектов overflow (вроде появления скролла, если контент чуть больше).


Способ 3: использовать только margin у одного уровня

Если ты контролируешь стили, проще вообще не задавать margin одновременно родителю и ребёнку. Например, делай отступы только у детей:

css
.item + .item {
  margin-top: 20px;
}

Тогда между элементами будет ровно 20 px, без сюрпризов.

Или наоборот — только у контейнера:

css
.container {
  margin: 20px;
}
/* у .item margin нет */

Способ 4: заменить margin на gap (если можно использовать Grid/Flex)

Для списков и сеток лучше вообще не использовать margin для отступов между элементами, а использовать gap:

css
.list {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

gap не схлопывается и ведёт себя предсказуемо. Это особенно удобно в PunBB‑шаблонах для списков тем, комментариев и т. п.


Частые места, где это реально «ломает» верстку

  1. Заголовки в контейнерах. Часто делают margin-top у заголовка и margin у секции — получается не тот отступ, который ожидаешь. Решение: либо только у заголовка, либо BFC у секции.
  2. Списки и карточки. Если у каждой карточки margin-bottom, а у списка тоже есть отступ — они схлопнутся. Решение: gap у списка или margin только у карточек, но не у обоих.
  3. Адаптив и масштабирование. При увеличении шрифта в браузере отступы могут «съезжать», если они завязаны на схлопывающиеся margin. Решение: фиксируй логику через BFC или gap.

Практический пример (список тем)

Допустим, у тебя список тем форума, и нужно, чтобы между темами был ровный отступ 16 px, а у самого списка сверху — 24 px.

Плохо (будет схлопывание):

css
.topics-list {
  margin-top: 24px;
}
.topic-item {
  margin-bottom: 16px;
}

Хорошо (гарантированный результат):

css
.topics-list {
  margin-top: 24px;
  display: flow-root; /* отключаем схлопывание с первым .topic-item */
}
.topic-item {
  margin-bottom: 16px;
}

Или через gap:

css
.topics-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  margin-top: 24px;
}
.topic-item {
  margin: 0; /* убираем margin у элементов */
}

Что выбрать в разных ситуациях

  • Нужно быстро и надёжно: display: flow-root у родителя.
  • Делаешь сетку/список: display: grid/flex + gap.
  • Нельзя менять структуру: добавь padding-top: 1px или border-top: 1px solid transparent у родителя.
  • Хочешь максимально чистую логику: используй margin только на одном уровне (либо у родителя, либо у детей).

Добавить комментарий