Перейти к содержанию

Почему блок шире, чем вы задали: блочная модель по-человечески

28 августа 2026 · 8 минут · Dediu Pavel

Вы написали ширину, а блок получился другим и вылез за родителя. Разбираем, как браузер считает размер на самом деле, и как за полминуты найти виновника в панели разработчика.

Симптом: поставил width: 100%, а блок вылез за экран

Выглядит это так. Внизу страницы появилась горизонтальная прокрутка. Или колонка уехала за правый край. Или четыре карточки, каждая по 300 пикселей, не поместились в контейнер шириной 1160, хотя 300 × 4 = 1200 и на бумаге всё сходилось. В CSS при этом ни одной опечатки: значения ровно те, что вы написали.

Важно сразу снять подозрение с браузера: он считает честно и одинаково во всех вкладках. Расхождение возникает между тем, что вы имели в виду под словом «ширина», и тем, что под ним понимает CSS. Эти два значения разные по умолчанию, и пока вы не сказали обратного, действует умолчание.

Причина почти всегда одна и та же: запись width: 300px не означает «блок шириной 300 пикселей». Она означает «содержимому блока — 300 пикселей», а отступы и границы прибавятся сверху. Разберём, откуда берётся разница, чтобы дальше получать нужное число расчётом, а не подбором width: 96%.

Что браузер на самом деле считает шириной: content-box по умолчанию

У любого блока четыре зоны, строго в этом порядке от центра наружу. content — область содержимого, именно её по умолчанию задают width и height. padding — внутренний отступ между содержимым и границей, закрашивается фоном элемента. border — граница, она занимает место даже прозрачная. margin — внешний отступ за границей, всегда прозрачный. Каждая зона добавляется со всех четырёх сторон.

Занимаемая ширина складывается так: width плюс padding слева и справа плюс border слева и справа. Считаем на карточке с width: 300px, padding: 24px и border: 1px solid: 300 + 24 + 24 + 1 + 1 = 350 пикселей. Ряд из четырёх таких карточек занимает 1400 — на 240 больше контейнера, и это ещё без промежутков между ними. В коде написано 300, в голове арифметика сходится, на экране нет.

Классический случай той же ошибки — width: 100% вместе с padding: 24px на одном элементе. Сто процентов считаются от ширины родителя, а 48 пикселей полей прибавляются к ней. Блок гарантированно шире родителя ровно на величину своих отступов, и появляется горизонтальная прокрутка.

border-box: что меняется и почему это ставят глобально

Свойство box-sizing переключает, что именно считать шириной. Значение по умолчанию — content-box: width относится к содержимому, отступы и границы идут сверху, наши 300 превращаются в 350. Значение border-box: width — это полная ширина вместе с отступами и границами, наши 300 остаются 300, а содержимому достаётся 300 − 48 − 2 = 250.

Второй вариант совпадает с тем, как человек думает о размерах: «карточка 300, внутри отступ 24». Поэтому border-box ставят глобально одной строкой в начале файла: *, *::before, *::after { box-sizing: border-box; }. Псевдоэлементы перечисляют отдельно, потому что звёздочка их не покрывает.

Стоит понимать, чего эта строка не делает. Она не «уменьшает блоки» и не «чинит вёрстку» — она делает так, что число в CSS и число на экране совпадают. После неё 4 × 300 = 1200 — всё ещё больше 1160, и сразу видно, что в ряд влезает три карточки, а не четыре. Считать стало можно в уме, и это единственная причина глобального правила.

Полезно посмотреть на это и с другой стороны: содержимому карточки при border-box достаётся 250 пикселей, и именно от этого числа считается всё внутри — длина строки, размер фотографии, ширина кнопки во всю карточку. При content-box внутреннее число вы знаете, а внешнее — нет, и сетка каждый раз получается на глаз.

Рядом полезно помнить про ограничители. width — это желаемая ширина, а min-width и max-width — границы, и они сильнее: max-width перебивает width, а min-width перебивает обе. Отсюда обязательная строка для картинок: у img есть собственный размер в пикселях, и фотография шириной 1600 порвёт колонку в 300, если не написать max-width: 100%.

Почему border-box не спасает от margin

margin не входит в ширину блока ни при каком значении box-sizing: в схеме он лежит снаружи границы. Но место в потоке он занимает, поэтому в расчёт ряда его добавляют отдельным слагаемым: три карточки по 300 с промежутками по 24 — это 300 × 3 + 24 × 2 = 948.

Следствие, на котором спотыкаются регулярно: width: 100% вместе с margin: 16px даёт переполнение всегда. Сто процентов считаются от ширины родителя, отступы прибавляются к ним, и border-box тут ни при чём — он про содержимое внутри границы, а не про то, что снаружи.

Правильное решение обычно не в подборе процентов. Блочный элемент и так занимает всю доступную ширину родителя, поэтому width: 100% ему чаще всего просто не нужно: уберите строку — и отступы перестанут выталкивать блок наружу. Второй вариант — перенести отступ на родителя в виде padding. Третий, самый предсказуемый, — задавать расстояния через gap у flex- или grid-контейнера, где эта арифметика не возникает вовсе.

Схлопывание отступов: это не баг, вот правило

Вторая половина вопросов про размеры звучит иначе: «отступ пропал» или «отступ вылез наружу». Вы поставили заголовку секции margin-top: 48px, а отступ появился не внутри секции, а над ней: сама секция вместе с фоном уехала вниз, а между её верхним краем и заголовком ноль. Это схлопывание вертикальных отступов, поведение из спецификации, включённое всегда, пока вы не сделали чего-то, что его выключает.

Случаев ровно три. Соседние братья: нижний отступ верхнего элемента и верхний отступ нижнего накладываются, остаётся больший из двух, а не сумма — 24 и 16 дают 24, а не 40. Родитель и первый или последний ребёнок: если между margin-top ребёнка и верхней границей родителя нет ни padding, ни border, отступ выходит наружу и двигает вниз самого родителя. Пустой блок: у блока без содержимого, padding, border и высоты собственные верхний и нижний отступы схлопываются между собой в один.

Правило выглядит произволом, пока не посмотреть на текст. У абзаца есть отступы сверху и снизу, примерно по строке. Без схлопывания между двумя соседними абзацами оказывалось бы двойное расстояние. Схлопывание превращает «отступ снизу» и «отступ сверху» в одно расстояние между элементами.

Не схлопывается: горизонтальные отступы — никогда; элементы внутри flex- и grid-контейнера, там отступы соседей складываются; элементы с position: absolute и fixed; блоки со своим контекстом форматирования — overflow: auto или hidden, display: flow-root, display: inline-block; и любой случай, где между двумя отступами есть padding или border, пусть даже в один пиксель.

Проверить это на себе можно за минуту. Возьмите секцию с фоном и заголовком, у которого стоит верхний отступ, и добавьте секции padding-top: 1px. Отступ мгновенно вернётся внутрь: между margin ребёнка и краем родителя появился padding, соприкосновение исчезло, схлопываться стало нечему. Один пиксель — не решение для боевого кода, но лучшее доказательство того, что дело именно в схлопывании.

Практический вывод короче теории: выберите одно направление и держитесь его. Если все вертикальные расстояния заданы через margin-bottom, схлопыванию между братьями нечего накладывать — отступ всегда один и всегда тот, что написан. Правило «сверху не отступаем» экономит больше времени, чем детальное знание спецификации.

Как найти виновника за 30 секунд в DevTools

Гадать не нужно, всё видно в браузере. Щёлкните по блоку правой кнопкой и выберите «Проверить», затем откройте вкладку Computed. В самом верху там схема блочной модели: четыре вложенных прямоугольника от margin снаружи до content в центре, с числами по каждой стороне.

Дальше три шага. Первый: посмотрите на центральный прямоугольник — это реальный размер содержимого. Если он равен вашему width, а не меньше на величину отступов, вы в content-box. Второй: найдите ниже в списке свойств строку box-sizing — она прямо говорит, по какому правилу считается ширина. Третий: наведите курсор на любую зону схемы, и соответствующая область подсветится на странице своим цветом: content синий, padding зелёный, border жёлтый, margin оранжевый. Оранжевая подсветка, нарисованная за пределами родителя, — это и есть ушедший наружу отступ.

Тот же приём отличает схлопывание от опечатки. Если в Computed стоит нужное значение margin-top, а над элементом пусто — это схлопывание, и CSS верен. Если в Computed ноль — ищите ошибку: опечатку в имени класса, забытые единицы (margin-top: 48 без px не применится) или более сильный селектор, перебивший ваш; проигравшие свойства в панели Styles зачёркнуты.

Это ровно тот способ работы, на котором построен курс «Вёрстка без магии»: разбираем поток, блочную модель, flex и grid до причин, а не до рецептов, и в результатах курса отдельным пунктом стоит «объяснять, почему блок получил именно такие размеры». На блочную модель там уходит целый урок с расчётами на бумаге и сверкой со схемой в Computed.

Пять типовых ситуаций и что в каждой чинить

Сводка, к которой удобно возвращаться.

Общее во всех пяти случаях: сначала выясняем, из чего сложилось число, и только потом правим. Подбор значений тем и плох, что чинит одну ширину экрана и оставляет ошибку на всех остальных.

Та же арифметика существует и в макете, просто под другими именами: внутренние поля контейнера и промежутки между элементами задаются там числами, а не подтягиванием мышью. Как это выглядит со стороны Figma и что делать, когда блоки разъезжаются уже в макете, разбираем в статье автолейаут в Figma разъезжается.

  • Блок с width: 100% и внутренними отступами вылез за родителя. Глобальный border-box в начале файла. Не подбирайте проценты: 96% решают задачу на одной ширине экрана и ломаются на другой.
  • Ряд карточек не помещается, хотя сумма ширин сходится. Пересчитайте занимаемую ширину с padding и border, а промежутки добавьте отдельным слагаемым. Если считать неудобно — переведите расстояния на gap.
  • Картинка порвала колонку. img { display: block; max-width: 100%; height: auto; }. Строка display: block заодно убирает щель под картинкой: по умолчанию она строчная и стоит на базовой линии текста.
  • Отступ уехал наружу секции, фон начинается не там. Схлопывание. Дайте секции padding — это честное решение, когда воздух внутри всё равно нужен, — либо уберите margin-top у первого ребёнка вовсе.
  • На телефоне появилась горизонтальная прокрутка. Ищите элемент с фиксированной шириной больше экрана, отрицательный отступ или длинное неразрывное слово. Контейнер надёжнее задавать как width: min(1160px, 100% - 32px): он сам ужимается вместе с полями.
Курс по теме

Вёрстка без магии

11 модулей · 22 часа · С нуля

Смотреть программу — €42