Skip to main content

Що таке злиття відступів у CSS

Margin collapsing (злиття відступів) - це поведінка CSS, коли два вертикальних відступи стикаються і замість їх суми береться лише більший.

Теорія

TL;DR

  • Зливаються тільки margin-top і margin-bottom. Горизонтальні відступи ніколи не зливаються.
  • Три сценарії: сусідні блоки, батько і перший/останній нащадок, порожні елементи.
  • Перемагає більший відступ. Якщо обидва рівні, результат дорівнює одному з них.
  • Flexbox, Grid і overflow: hidden вимикають злиття.
  • Тільки нормальний потік. Абсолютно позиціоновані та плаваючі елементи не зливаються.

Швидкий приклад

css
.card-top { margin-bottom: 40px; } .card-bottom { margin-top: 24px; } /* Фактичний проміжок між ними: 40px, не 64px */

Браузер бере більше значення. 24px просто зникають.

Три сценарії злиття

Сусідні блоки - найпоширеніший випадок. Коли margin-bottom одного блока межує з margin-top наступного, вони зливаються в один відступ. Саме це дивує розробників вперше, коли вони задають відступи між картками або прев'ю статей.

Батько і перший/останній нащадок - складніший сценарій. Якщо в батьківського елемента немає padding-top, border-top і жодного inline-контенту перед першим нащадком, margin-top нащадка зливається з margin-top батька. Відступ ніби вистрибує з батьківського блока. Те саме відбувається знизу з останнім нащадком.

html
<section> <!-- немає padding або border --> <p style="margin-top: 32px;">Перший абзац</p> </section>

Весь section зсувається вниз на 32px. Абзац всередині не рухається відносно section.

Порожні блоки зливають свій верхній і нижній відступ в один. В сучасному коді це рідко стає проблемою, але пояснює дивні пробіли в старій розмітці.

Як зупинити злиття

Для сценарію батько-нащадок будь-який з цих варіантів на батьківському елементі:

  • padding-top або padding-bottom
  • border (навіть 1px solid transparent)
  • overflow: hidden або overflow: auto
  • display: flow-root (найчистіший варіант, без побічних ефектів)
  • display: flex або display: grid

Для злиття сусідніх блоків рішення знаходиться на рівні контейнера. Перевести обгортку в display: flex з gap - і відступи більше не зливатимуться.

Типові помилки

Найпоширеніша: розробник додає margin-top: 40px до заголовка всередині section, а потім дивується чому весь section зсунувся вниз, а не заголовок. Це злиття батька з нащадком. Одна строчка виправляє: display: flow-root на батьківському елементі.

Ще часта ситуація: два компоненти з margin-bottom: 30px і margin-top: 30px відповідно. Розробник очікує 60px між ними, отримує 30px. Нічого не зламалось - злиття працює за специфікацією. Я бачив, як це викликало тривалий дебагінг при передачі дизайну, де відступи збігалися з Figma, але розробник очікував що маржини підсумовуватимуться.

Від'ємні відступи мають свої правила. Якщо один відступ -20px, а інший 30px, результат буде 10px (вони складаються, коли знаки різні). Два від'ємних відступи дають найбільший за модулем від'ємний.

Follow-up питання

Q: Чи зливаються відступи всередині flex або grid контейнера?
A: Ні. Обидва формати створюють новий контекст форматування (block formatting context), який вимикає злиття для прямих нащадків.

Q: Що відбувається, якщо один відступ від'ємний?
A: Від'ємний і позитивний відступи складаються. Тобто 30px і -10px дадуть 20px. Два від'ємних дають найбільший за модулем від'ємний.

Q: display: flow-root кращий за overflow: hidden для цього?
A: Для цієї конкретної задачі - так. overflow: hidden обрізає контент, що виходить за межі батька, що зазвичай небажано. display: flow-root створює контекст форматування без жодного обрізання.

Q: Чи можуть горизонтальні відступи зливатись?
A: Ні. Специфікація CSS описує злиття тільки для вертикальних відступів у нормальному потоці.

Приклади

Відступи між сусідами, які не підсумовуються

html
<article class="post">...</article> <article class="post">...</article>
css
.post { margin-top: 32px; margin-bottom: 32px; } /* Проміжок між двома постами: 32px, а не 64px */

Кожен пост оголошує 32px з обох боків, але між будь-якими двома постами тільки 32px загалом, не 64px. Для передбачуваних відступів у карткових layouts краще використовувати gap на flex або grid контейнері.

Маржин нащадка, що вистрибує з батька

css
.hero { background: #f0f0f0; /* Без padding або border */ } .hero h1 { margin-top: 48px; }
html
<section class="hero"> <h1>Вітаємо</h1> </section>

margin-top у h1 зливається з margin-top section. Весь section зсувається вниз на 48px, а не тільки заголовок. Додати padding-top: 1px до .hero або виставити display: flow-root - і відступ залишиться всередині.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?