Flexbox и Grid: современные механизмы раскладки
Flexbox и Grid — два основных механизма раскладки в современном CSS. Flexbox раскладывает элементы вдоль одной оси, Grid — одновременно по строкам и колонкам. Чтобы не теряться в «магии» раскладки, нужно понимать алгоритмы распределения пространства: в Flexbox это flex-basis → flex-grow → flex-shrink, в Grid — единицы fr и функция minmax. Понимание этих механизмов и различий между ними — ключ к уверенной вёрстке любых интерфейсов.
Содержание
- Глубокий разбор: Flexbox
- Глубокий разбор: Grid
- Flexbox vs Grid
- Практические примеры
- Типичные ошибки и антипаттерны
- Ключевые тезисы для интервью
- Заключение
- Полезные ссылки
Глубокий разбор: Flexbox
Две оси: main axis и cross axis
Каждый flex-контейнер задаёт две оси.
- Main axis (главная ось) — направление, вдоль которого располагаются flex-элементы. Задаётся
flex-direction. - Cross axis (поперечная ось) — перпендикулярно главной.
.flex-row {
display: flex;
flex-direction: row; /* main axis слева направо (по умолчанию) */
}
.flex-column {
display: flex;
flex-direction: column; /* main axis сверху вниз */
}
flex-direction принимает значения row, row-reverse, column, column-reverse. Вместе с writing-mode они определяют, где физическое начало и конец осей. Поэтому важны логические свойства:
justify-contentвыравнивает по main axis.align-itemsвыравнивает по cross axis.
Свойства justify-* всегда относятся к main axis, align-* — к cross axis. Это справедливо и для Grid.
Flex-элементы: что происходит с детьми
Прямые дети flex-контейнера становятся flex-элементами. При этом:
floatиclearне работают.vertical-alignигнорируется.- Margin’ы не схлопываются.
- Анонимные текстовые узлы оборачиваются в анонимные flex-элементы.
По умолчанию каждый flex-элемент:
flex: 0 1 auto;
Это означает: не расти (flex-grow: 0), сжиматься при необходимости (flex-shrink: 1), базовый размер по контенту (flex-basis: auto).
flex-basis vs width
flex-basis — это начальный размер flex-элемента до распределения свободного места. Для flex-direction: row flex-basis заменяет width; для column — height.
.item {
flex-basis: 200px;
}
Ключевые отличия от width:
flex-basisработает только во flex-контексте.- Если заданы и
flex-basis, иwidth(для row), приоритет уflex-basis, кроме значенияautoуflex-basis— тогда используетсяwidth. flex-basis: contentзадаёт размер по контенту, включая возможность переноса строк.
Важный нюанс: flex-basis учитывает box-sizing. Если у элемента box-sizing: border-box, то flex-basis: 200px включает padding и border.
flex-grow: как делится лишнее место
flex-grow определяет, как flex-элемент забирает свободное пространство вдоль main axis. Значение — не процент, а коэффициент.
.container {
display: flex;
width: 600px;
}
.a { flex-grow: 1; }
.b { flex-grow: 2; }
Если суммарный базовый размер элементов меньше 600px, оставшееся место делится в пропорции 1:2. Важно: делится не вся ширина контейнера, а только свободное пространство.
Если flex-basis: 0, элемент как будто не имеет собственного размера, и весь контейнер делится пропорционально flex-grow.
.equal {
flex: 1 1 0; /* равные колонки независимо от контента */
}
flex-shrink: как сжимается при нехватке места
flex-shrink определяет, как элемент уменьшается, когда сумма базовых размеров превышает размер контейнера.
.container {
display: flex;
width: 400px;
}
.a { flex-basis: 300px; flex-shrink: 1; }
.b { flex-basis: 300px; flex-shrink: 2; }
Избыток: 300 + 300 − 400 = 200px. Он распределяется обратно пропорционально flex-shrink с учётом базового размера. Элемент b сожмётся сильнее, чем a, но не ровно вдвое — формула учитывает flex-basis * flex-shrink.
Если нужно запретить сжатие:
.item {
flex-shrink: 0;
}
Shorthand flex
Свойство flex объединяет flex-grow, flex-shrink и flex-basis. Возможны три ключевых значения:
flex: initial; /* 0 1 auto — по умолчанию */
flex: auto; /* 1 1 auto — расти и сжиматься от размера контента */
flex: none; /* 0 0 auto — фиксированный размер, не сжиматься */
flex: 1; /* 1 1 0% — занять всё доступное место */
Частая ошибка: flex: 1 устанавливает flex-basis: 0%, а не auto. Это важно, когда контент элементов сильно различается: с flex: 1 колонки будут равными, с flex: auto — пропорциональными контенту.
Выравнивание
justify-content — по main axis
.container {
justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly;
}
space-between— первый и последний элементы у краёв, остальные равномерно.space-around— равные промежутки с половинками по краям.space-evenly— все промежутки, включая краевые, равны.
align-items — по cross axis для всех элементов в строке
.container {
align-items: stretch | flex-start | flex-end | center | baseline;
}
По умолчанию stretch: элементы растягиваются на всю высоту строки.
align-self — переопределение для одного элемента
.item {
align-self: flex-start;
}
align-content — распределение строк при переносе
Работает только тогда, когда flex-wrap: wrap и есть несколько строк. Управляет промежутками между строками по cross axis.
.container {
flex-wrap: wrap;
align-content: flex-start | flex-end | center | space-between | space-around | space-evenly | stretch;
}
Без align-content строки растягиваются и распределяются по умолчанию. Если высота контейнера больше суммы высот строк, align-content решает, как заполнить пространство.
flex-wrap и многострочность
По умолчанию flex-wrap: nowrap — все элементы в одну строку. Для переноса:
.container {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
При переносе каждая строка становится отдельной линией для выравнивания. align-items работает внутри строки, align-content — между строками.
gap и order
gap задаёт расстояние между элементами по main и cross axis:
.container {
gap: 16px 24px; /* row-gap column-gap */
}
order меняет визуальный порядок элементов. По умолчанию 0; элементы с меньшим order идут раньше.
.first { order: -1; }
Важно: order меняет только визуальный порядок, не DOM-порядок. Это критично для accessibility: скринридеры и фокус по-прежнему следуют исходному порядку.
Минимальные размеры и переполнение
По умолчанию flex-элементы не могут быть меньше минимального размера контента (min-width: auto). Это часто приводит к тому, что элементы не сжимаются до нуля, даже при flex-shrink: 1.
.item {
min-width: 0; /* разрешить сжатие до нуля */
overflow: hidden;
}
Это особенно важно для текстовых блоков внутри flex-контейнера: без min-width: 0 текст не будет обрезаться text-overflow: ellipsis.
Глубокий разбор: Grid
Explicit и implicit grid
Explicit grid (явная сетка) — это треки, которые вы задали явно через grid-template-columns, grid-template-rows или grid-template-areas.
Implicit grid (неявная сетка) — треки, которые браузер добавляет автоматически, когда элементов больше, чем явных ячеек, или когда элемент выходит за границы явной сетки.
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 100px;
}
Здесь три колонки заданы явно. Если элементов больше девяти, браузер создаст дополнительные строки высотой 100px — это implicit grid.
Размер implicit-треков управляется grid-auto-columns и grid-auto-rows. Если они не заданы, implicit треки получают размер по контенту.
Единица fr
fr (fraction, доля) — это гибкая единица, которая распределяет доступное пространство после вычета фиксированных треков и gap’ов.
.grid {
display: grid;
grid-template-columns: 200px 1fr 2fr;
}
После вычета 200px оставшееся место делится в пропорции 1:2. Важно: fr распределяет именно свободное пространство, а не всю ширину. Если контент в 1fr больше, чем его доля, трек может стать шире — но не шире доступного места.
fr нельзя напрямую комбинировать с auto в одном выражении, но можно писать 1fr auto 2fr. В таком случае auto сначала займёт место под контент, а fr поделит остаток.
minmax()
Функция minmax(min, max) задаёт диапазон размеров трека. Трек не будет уже min и не шире max.
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(200px, 1fr));
}
Каждая колонка занимает равную долю, но не менее 200px. Когда места станет меньше 600px, Grid создаст переполнение, если не задан auto-fit/auto-fill.
Частое сочетание:
.grid {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
Это создаёт адаптивную сетку: колонки не уже 280px, а свободное место распределяется поровну.
auto-fit vs auto-fill
repeat() может принимать auto-fit или auto-fill вместо фиксированного числа повторений.
auto-fill— создаёт столько треков, сколько помещается в контейнер, даже пустых.auto-fit— создаёт столько треков, сколько есть элементов, и схлопывает пустые треки до нуля.
.fill {
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.fit {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
Разница заметна, когда элементов меньше, чем может поместиться. В auto-fill останутся пустые треки; в auto-fit оставшееся место распределится между заполненными.
Для большинства интерфейсов используйте auto-fit: оно даёт поведение «растянуть элементы на всю ширину».
grid-template-areas
Именованные области позволяют описывать макет визуально:
.layout {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
Преимущества:
- Макет читается как ASCII-арт.
- Перестроение на мобильных делается сменой
grid-template-areas, а не селекторов. - Области должны быть прямоугольниками без разрывов.
Размещение элементов: линии и span
Элемент можно разместить по номерам линий:
.item {
grid-column: 1 / 3;
grid-row: 2 / span 2;
}
span 2 означает «занять два трека». Отрицательные номера линий отсчитываются с конца: grid-column: 1 / -1 растянет элемент на всю ширину.
Alignment в Grid
В Grid те же свойства, что и в Flexbox, но применяются к двум осям одновременно:
justify-items— выравнивание содержимого ячеек по inline-оси (горизонталь).align-items— выравнивание по block-оси (вертикаль).justify-content— выравнивание всей сетки, если она уже контейнера.align-content— то же по вертикали.justify-self/align-self— переопределение для отдельного элемента.
.grid {
display: grid;
place-items: center; /* сокращение для align-items + justify-items */
}
Gap
.grid {
gap: 16px 24px; /* row-gap column-gap */
}
В отличие от margin’ов, gap не схлопывается и не создаёт лишних отступов по краям.
Subgrid
Subgrid позволяет вложенному grid-контейнеру наследовать треки родительской сетки.
.parent {
display: grid;
grid-template-columns: 200px 1fr 200px;
}
.child {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
}
Элемент .child занимает всю ширину родителя и использует его же колонки. Это решает проблему выравивания контента внутри вложенных компонентов по общей сетке.
На момент написания subgrid поддерживается в современных Firefox, Chrome, Edge и Safari. Для продакшена стоит проверять через @supports:
.child {
display: grid;
}
@supports (grid-template-columns: subgrid) {
.child {
grid-template-columns: subgrid;
}
}
Implicit vs explicit placement
Если не задать grid-column/grid-row, элемент размещается по алгоритму auto-placement: слева направо, сверху вниз, заполняя пустые ячейки. Свойство grid-auto-flow управляет направлением:
.grid {
grid-auto-flow: row; /* по умолчанию */
}
.grid-dense {
grid-auto-flow: row dense; /* заполнять пустоты при наличии */
}
dense пытается заполнить дырки, оставшиеся от больших элементов, но это может нарушить визуальный порядок.
Flexbox vs Grid
- Flexbox — одномерная раскладка: распределение вдоль одной оси (main axis), перенос на новую строку через
flex-wrap. - Grid — двумерная раскладка: строки и колонки задаются независимо, элементы размещаются по линиям или именованным областям.
- Выбор: для общей структуры страницы (шапка, сайдбар, контент, футер) — Grid; для распределения элементов внутри одной строки или колонки (панель, список кнопок, строка сайдбар+контент) — Flexbox.
- Выравнивание: набор свойств
justify-*/align-*общий, но в Flexboxjustify-contentработает вдоль main axis, а в Gridjustify-items— по inline-оси внутри ячеек. - Адаптивность: и Grid (
auto-fit/auto-fill+minmax), и Flexbox (flex-wrap) позволяют строить адаптивные раскладки без медиа-запросов, но Grid делает это точнее. - Гибкость размера:
frв Grid иflex-grow/flex-shrinkв Flexbox оба распределяют именно свободное пространство, а не всю ширину контейнера.
Практические примеры
Пример 1: равные колонки (Flexbox)
<div class="row">
<div class="col">A</div>
<div class="col">B</div>
<div class="col">C</div>
</div>
.row {
display: flex;
gap: 16px;
}
.col {
flex: 1 1 0;
}
Все три колонки одинаковой ширины независимо от контента, потому что flex-basis: 0 и flex-grow равны.
Пример 2: фиксированный сайдбар и гибкий контент (Flexbox)
<div class="layout">
<aside class="sidebar">Sidebar</aside>
<main class="content">Main content</main>
</div>
.layout {
display: flex;
gap: 24px;
}
.sidebar {
flex: 0 0 240px;
}
.content {
flex: 1 1 auto;
min-width: 0;
}
Сайдбар фиксированной ширины, основной контент занимает оставшееся место. min-width: 0 позволяет контенту сжиматься.
Пример 3: адаптивная сетка карточек (Grid)
<div class="cards">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
</div>
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
Карточки растягиваются на всю ширину, но не уже 280px. При уменьшении viewport они переносятся.
Пример 4: классический layout с областями (Grid)
<div class="layout">
<header class="header">Header</header>
<aside class="sidebar">Sidebar</aside>
<main class="main">Content</main>
<footer class="footer">Footer</footer>
</div>
.layout {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
gap: 16px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
@media (max-width: 768px) {
.layout {
grid-template-columns: 1fr;
grid-template-rows: auto auto 1fr auto;
grid-template-areas:
"header"
"sidebar"
"main"
"footer";
}
}
Перестроение на мобильных достигается только изменением grid-template-areas.
Пример 5: subgrid для вложенных карточек
<div class="products">
<article class="product">
<h3>Product A</h3>
<p>Description</p>
<span class="price">$10</span>
</article>
<article class="product">
<h3>Product B with longer name</h3>
<p>Another description</p>
<span class="price">$20</span>
</article>
</div>
.products {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.product {
display: grid;
grid-template-rows: auto 1fr auto;
gap: 8px;
}
@supports (grid-template-rows: subgrid) {
.product {
grid-template-rows: subgrid;
grid-row: span 3;
}
}
С subgrid заголовки, описания и цены разных карточек выравниваются по общей сетке строк.
Типичные ошибки и антипаттерны
- Использование Grid там, где достаточно Flexbox. Grid — для двумерных макетов; Flexbox — для одномерных распределений. Для списка в одну строку часто проще Flexbox.
- Путаница
flex-basisиwidth.flex-basisопределяет начальный размер до распределения пространства, аwidth— фактический размер только еслиflex-basis: auto. В flex-контексте предпочтительнее управлять размерами черезflex-basis. - Использование
flex: 1для «одинаковых колонок» без пониманияflex-basis: 0%. Это работает, но если нужна пропорциональность контенту, используйтеflex: auto. - Ожидание, что
align-contentработает безflex-wrap.align-contentвлияет только на распределение строк, поэтому без многострочности игнорируется. - Забытое
min-width: 0при переполнении текста. По умолчанию flex-элемент не может быть уже контента, иtext-overflowне сработает. - Использование
orderдля изменения логического порядка. Визуальный порядок не должен расходиться с порядком в DOM — это ломает accessibility и навигацию с клавиатуры. - Путаница
auto-fitиauto-fill.auto-fillоставляет пустые треки;auto-fitсхлопывает их. В 90% случаев нуженauto-fit. - Ожидание, что
frучитывает контент.frделит доступное пространство, но треки всё равно уважают минимальный размер контента. Для строгих ограничений используйтеminmax(0, 1fr). - Переполнение с
minmax(200px, 1fr)безauto-fit/auto-fill. Фиксированное число колонок сminmaxдаст горизонтальный скролл, если места не хватит. - Неправильные области в
grid-template-areas. Область должна быть сплошным прямоугольником. Разорванная фигура вызовет ошибку. - Subgrid без fallback. Subgrid не везде поддерживается; проверяйте через
@supports.
Ключевые тезисы для интервью
- Flexbox раскладывает элементы вдоль main axis (
flex-direction):justify-*выравнивает по main axis,align-*— по cross axis;flex-basisзадаёт начальный размер до распределения пространства. flex-growделит только свободное пространство пропорционально коэффициентам, а при переполненииflex-shrinkсжимает элементы по формулеflex-basis * flex-shrink. Шортханды:flex: 1=1 1 0%(равные колонки),flex: auto=1 1 auto(пропорционально контенту).- Flex-элементы по умолчанию имеют
min-width: autoи не могут стать уже контента, поэтомуtext-overflow: ellipsisработает только послеmin-width: 0;orderменяет лишь визуальный порядок и при расхождении с DOM ломает доступность. - Grid раскладывает элементы одновременно по строкам и колонкам; явные треки задаются через
grid-template-*, а при выходе за границы браузер создаёт implicit треки, управляемыеgrid-auto-rows/grid-auto-columns. frделит только свободное пространство (остаток после фиксированных треков и gap’ов), а не всю ширину контейнера, как%;repeat(auto-fit, minmax(250px, 1fr))создаёт адаптивную сетку без медиа-запросов, гдеauto-fitсхлопывает пустые треки, аauto-fill— оставляет.grid-template-areasописывает макет ASCII-диаграммой и перестраивается сменой значений в@media; области должны быть прямоугольниками. Subgrid позволяет вложенной сетке наследовать треки родителя, выравнивая вложенные компоненты по общей сетке.- Grid выбирают для двумерных страничных макетов, Flexbox — для одномерных распределений вдоль одной оси (панель инструментов, строка с сайдбаром).
Заключение
Flexbox и Grid дополняют друг друга. Flexbox решает задачу одномерной раскладки: понимание алгоритма flex-grow/flex-shrink/flex-basis и осей позволяет точно управлять размерами без магических значений. Grid — двумерная система для страничных структур: fr с minmax() и auto-fit создают адаптивные сетки без медиа-запросов, а grid-template-areas делает макет читаемым и легко перестраиваемым. Правило выбора простое: общая структура страницы — Grid, распределение по одной оси — Flexbox.