Flexbox в деталях: оси, grow/shrink/basis, паттерны
Flexbox — модуль раскладки для одномерного потока: строка или колонка элементов, которые должны гибко делить между собой свободное место. Если каскад из предыдущей главы — это «правила игры», то flexbox — первый «спортивный инвентарь»: с ним ты собираешь навигации, карточки, тулбары и формы. В краткой версии ты применял flex и justify-center наугад. Здесь разбираем механику: как считаются размеры айтемов, почему контент «не сжимается» и почему одно свойство ломает всю раскладку.
В продакшене flex — самый частый инструмент раскладки компонентного уровня: кнопка с иконкой, карточка с футером, шапка с лого. А вместе с ним — самые частые баги: переполнение текстом, «лишний» скролл, айтемы разной высоты. Всё это решается пониманием трёх свойств flex-grow, flex-shrink, flex-basis и одного дефолта min-width: auto.
Две оси: ментальная модель
Заголовок раздела «Две оси: ментальная модель»Flex-контейнер живёт в системе координат из двух осей. flex-direction задаёт главную ось, вторая — поперечная:
flex-direction: row (дефолт) flex-direction: column
main axis ──────────────────▶ │ ┌────┐ ┌────┐ ┌────┐ ▼ main axis │ A │ │ B │ │ C │ ┌────┐ └────┘ └────┘ └────┘ │ A │ ▲ ▲ ▲ └────┘ │ │ │ ┌────┐ cross axis (вертикаль) │ B │ └────┘ ┌────┐ │ C │ └────┘ ◀────┘ cross axis (горизонталь)Правило-мнемоника: justify-* всегда вдоль main, align-* всегда вдоль cross — независимо от направления. justify-content в column-режиме выравнивает по вертикали. Запомнил это — половина путаницы с flex ушла. Все значения всех flex-свойств с наглядными графиками собраны в классической шпаргалке CSS Tricks: A Complete Guide to Flexbox — держи её открытой, пока идёшь по этой главе.
Контейнер: все свойства
Заголовок раздела «Контейнер: все свойства».nav { display: flex; /* или inline-flex — контейнер в строке */ flex-direction: row; /* row | row-reverse | column | column-reverse */ flex-wrap: nowrap; /* nowrap | wrap | wrap-reverse */ gap: 1rem; /* отступы МЕЖДУ айтемами: row-gap + column-gap */}gap — важное новшество эпохи flex (раньше мучились с margin’ами и отрицательными margin’ами на контейнере). Пробелы появляются только между айтемами, по краям — чисто.
flex-flow — шорткат direction + wrap: flex-flow: row wrap.
justify-content: распределение вдоль main
Заголовок раздела «justify-content: распределение вдоль main»justify-content:
flex-start (дефолт) center flex-end┌────┐┌────┐┌────┐ ┌────┐┌────┐┌────┐ ┌────┐┌────┐┌────┐└────┘└────┘└────┘ └────┘└────┘└────┘ └────┘└────┘└────┘
space-between space-around space-evenly┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐└────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘(края вплотную) (полу-пустоты по краям) (равные пустоты везде)align-items: выравнивание вдоль cross (одна строка)
Заголовок раздела «align-items: выравнивание вдоль cross (одна строка)»align-items:
stretch (дефолт) flex-start center┌────┐ ┌──────────┐ ┌──┐ ┌────┐ ┌────┐ ┌──┐ ┌────┐ ┌────┐ ┌──┐│ │ │ │ │ │ └────┘ └────┘ └──┘ │ │ │ │ │ │└────┘ └──────────┘ └──┘ └────┘ └────┘ └──┘(вытянуты по высоте самого высокого)
baseline flex-end┌────┐ ┌──────────┐ ┌────┐│ │ │ │ │ │ ┌────┐└────┘ └──────────┘ └────┘ │ │(по базовой линии текста) └────┘ └──┘align-content: распределение СТРОК вдоль cross (при flex-wrap)
Заголовок раздела «align-content: распределение СТРОК вдоль cross (при flex-wrap)»Работает только когда айтемов больше одной строки (flex-wrap: wrap и реальное перенасыщение). Принимает те же значения, что и justify-content (stretch, flex-start, center, space-between, …). Если строка одна — не влияет ничего, и это классическая грабля: «почему align-content: center не центрирует мой одинокий айтем?» — потому что для одной строки есть align-items.
Айтемы: grow, shrink, basis — формула раскладки
Заголовок раздела «Айтемы: grow, shrink, basis — формула раскладки»Каждый flex-айтем имеет три крана, регулирующих его размер:
.item { flex-grow: 0; /* могу ли я РАСТИ, если есть свободное место? (коэффициент) */ flex-shrink: 1; /* могу ли я СЖИМАТЬСЯ, если места не хватает? (коэффициент) */ flex-basis: auto; /* мой стартовый размер до распределения (как width по main) */}Сценарий с grow. Контейнер 800px, три айтема flex-basis: 200px каждый → сумма 600px, свободных 200px. Если у всех flex-grow: 1, свободное место делится поровну: каждый получает 200 + 66.67 = 266.67px. Если первому flex-grow: 2, остальным 1: свободные 200px делятся на 4 доли (2+1+1), первый забирает 100px → 300px, остальные по 50px → 250px.
flex-grow: 2, 1, 1 при контейнере 800 и basis 200:
свободных 200px → 4 доли → 1 доля = 50px
┌────────── 300 ──────────┐┌──── 250 ────┐┌──── 250 ────┐│ flex-grow: 2 ││ grow: 1 ││ grow: 1 │└─────────────────────────┘└─────────────┘└─────────────┘Сценарий со shrink. Контейнер 500px, три айтема по flex-basis: 200px (сумма 600, не хватает 100). Все с flex-shrink: 1: сжатие распределяется пропорционально basis: каждый теряет 100 × (200/600) = 33.3 → по 166.67px. Формула общая:
вклад айтема в сжатие = недостача × (basis × shrink) / Σ(basis × shrink)Важно: shrink работает относительно basis, а не текущего размера. flex: 1 на узком айтеме сожмёт его меньше, чем flex: 1 на широком, — пропорция сохраняется. Браузерные алгоритмы этого перераспределения шаг за шагом разобраны в MDN: Контролирование соотношения элементов по главной оси — читать сразу после этой главы.
Шорткаты flex
Заголовок раздела «Шорткаты flex»flex: 1; /* = 1 1 0% — расти, сжиматься, стартовать с нуля. Все одинаковые flex:1 поделят место ПОРОВНУ */flex: auto; /* = 1 1 auto — расти/сжиматься, стартовать от контента */flex: initial; /* = 0 1 auto — дефолт: не расти, сжиматься, от контента */flex: none; /* = 0 0 auto — жёсткий бокс: не расти и не сжиматься */Это ключ к загадке «я задал flex: 1, а айтемы разной ширины»: flex: 1 — это flex-basis: 0%, размеры должны быть равны… если контент позволяет. Если внутри длинный текст — в дело вступает min-width: auto (см. грабли).
align-self и order
Заголовок раздела «align-self и order».item--tall { align-self: stretch } /* переопределение align-items для одного айтема */.item--first { order: -1 } /* визуальный порядок: по умолчанию 0, меньше — раньше */order меняет только ВИЗУАЛЬНЫЙ порядок: для screen reader’ов, фокуса по Tab и поисковиков айтем остаётся на месте в DOM. Выносить кнопку «Купить» первой через order — ломать доступность.
Грабля №1: min-width: auto и переполнение
Заголовок раздела «Грабля №1: min-width: auto и переполнение»Дефолт у flex-айтема: min-width: auto (и min-height: auto для column-оси). То есть айтем не может стать уже своего контента. Длинное слово или непрерывная строка растянет flex-строку, родитель получит горизонтальный скролл, и ни flex-shrink, ни overflow внутри сами по себе не помогут.
.item { min-width: 0; /* разрешаем сжиматься ниже контента */}.item .text { overflow: hidden; text-overflow: ellipsis; /* и красиво обрезаем */ white-space: nowrap;}ДО min-width: 0: ПОСЛЕ:
контейнер ──────────────────────▶ контейнер ────────────────▶┌────────────┐┌──────────────────────────────────┐ ┌───────┐┌──────────────────┐│ sidebar ││ вот-очень-длинная-строка-которая │ │sidebar││ вот-очень-длинна…││ 200px ││ не-сворачивается-и-тянет-скролл │ │ ││ │└────────────┘└──────────────────────────────────┘ └───────┘└──────────────────┘ ▲ скролл на весь контейнер контент обрезанТо же самое в column-режиме с высотой: min-height: 0 на flex-айтеме — обязательное условие, чтобы внутренний overflow: auto заработал и дал скролл внутри, а не раздул всю страницу.
Типовые паттерны
Заголовок раздела «Типовые паттерны»Идеальное центрирование
Заголовок раздела «Идеальное центрирование»<div class="hero"> <h1>Привет</h1></div>.hero { display: flex; justify-content: center; /* по main */ align-items: center; /* по cross */ min-height: 100dvh;}До flex это были position: absolute + transform или таблицы. Сейчас — две строки.
Карточки одинаковой высоты в строке
Заголовок раздела «Карточки одинаковой высоты в строке»<div class="cards"> <article class="card"> <h3>Заголовок</h3> <p>Текст разной длины…</p> <a class="card__link" href="#">Читать</a> </article> <!-- ещё карточки --></div>.cards { display: flex; gap: 1rem; align-items: stretch; /* дефолт: все растянутся до высоты ряда */}
.card { flex: 1; /* равные ширины */ display: flex; flex-direction: column; /* внутри — вертикальный флекс */}
.card__link { margin-top: auto; /* прижимаем футер карточки к низу */}Приём «колонка внутри строки»: внешний flex делает равную высоту, внутренний column-флекс с margin-top: auto у футера — разводит контент. Это заменяет абсолютное позиционирование и грид-хаки для карточек.
Sidebar + main
Заголовок раздела «Sidebar + main».layout { display: flex; gap: 2rem; align-items: flex-start; /* колонки не растягиваем по высоте друг друга */}.sidebar { flex: 0 0 260px; /* жёсткая колонка: не расти, не жаться, 260px */ /* эквивалент: width: 260px; flex-shrink: 0; */}.main { flex: 1; /* занимает всё остальное */ min-width: 0; /* и даём своему контенту сжиматься! */}Шапка: лого слева, меню по центру, пользователь справа
Заголовок раздела «Шапка: лого слева, меню по центру, пользователь справа».header { display: flex; align-items: center; gap: 1rem;}.header__menu { margin-inline: auto; /* авто-маргины поглощают свободное место */}Классика: margin-left: auto на последнем элементе прижимает его вправо, margin-inline: auto на среднем центрирует. Никаких float и абсолютов.
Sticky footer: прижать футер к низу при малом контенте
Заголовок раздела «Sticky footer: прижать футер к низу при малом контенте»<body class="page"> <header>…</header> <main class="page__main">…</main> <footer>…</footer></body>.page { display: flex; flex-direction: column; min-height: 100dvh;}.page__main { flex: 1; /* растягивается, выталкивая футер вниз */}При переполнении страница просто растёт — скролл работает естественно. Старые хаки с min-height: 100% на html/body больше не нужны.
Типичные ошибки и грабли
Заголовок раздела «Типичные ошибки и грабли»- Горизонтальный скролл из-за длинного контента в flex-строке — грабля
min-width: auto. Лечитсяmin-width: 0на айтеме +overflowна контенте. flex: 1, а айтемы разной ширины. Контент шире базы съедает «равенство». Лечится тем жеmin-width: 0или явнымflex-basis: 0+ обрезкой контента.- Пробелы-«буквы» между inline-block элементами — наследие старого подхода; при
display: flexпробелы в разметке не создают отступов, используй flex. - Центрирование через
justify-contentв column-режиме «не работает». Помни мнемонику: justify — всегда вдоль main; в column main — вертикаль. Хочешь по горизонтали в column — этоalign-items. align-contentна одной строке. Работает только при переносе на несколько строк; одну строку центрируетalign-items.- Менять визуальный порядок через
order, забывая про Tab и скринридер. Визуальный порядок ≠ порядок фокуса. Для доступности порядок правят в DOM. height: 100%на flex-айтеме «не работает». В flex-строке высота определяется align-items/stretch; используйalign-self: stretch(дефолт) и задавай минимальную высоту контейнеру, а не проценты айтему.- Делать gap отрицательным «чтобы карточки встали по краям». Для этого есть раскладка (отрицательные margin на обёртке для компенсации) или, правильнее, grid с
auto-fill.
Вопросы на собеседовании
Заголовок раздела «Вопросы на собеседовании»- Чем отличается
justify-contentотalign-items? Работают по разным осям: justify — вдоль главной (main), align — вдоль поперечной (cross). Приflex-direction: columnих роли меняются визуально, но не семантически. - Что делает
flex: 1и чем оно отличается отflex-grow: 1?flex: 1=flex-grow: 1; flex-shrink: 1; flex-basis: 0%— старт от нуля, поровну.flex-grow: 1при прочих дефолтах (shrink: 1, basis: auto) стартует от контента: размеры будут равны только если контент одинаков. - Как браузер распределяет свободное место при разных flex-grow?
Свободное место делится пропорционально коэффициентам grow. Айтем с grow 2 получит в два раза больше добавочных пикселей, чем с grow 1:
итог = basis + свободное × (grow / Σgrow). - Почему flex-элемент не сжимается меньше контента и как это исправить?
Дефолт
min-width: autoзапрещает.min-width: 0снимает ограничение; обычно в паре сoverflow: hiddenиtext-overflow: ellipsisна текстовом контенте. - Что такое
align-contentи когда он работает? Распределение flex-СТРОК вдоль cross-оси. Активен только приflex-wrap: wrapи наличии двух и более строк; с одной строкой не влияет. - Как прижать футер к низу страницы независимо от контента?
body — column-флекс на
min-height: 100dvh, main —flex: 1. При коротком контенте растягивается и выталкивает футер, при длинном страница растёт естественно. orderвлияет ли на доступность / порядок табуляции? Нет: меняет только визуальный порядок. DOM, фокус по Tab и порядок для assistive-технологий остаются прежними — поэтому менять порядок важных интерактивных элементов черезorderнельзя.- Когда flex, а когда grid? Flex — одномерное расположение (ряд или колонка компонентов с гибким распределением места). Grid — двумерные сетки (колонки И строки одновременно). Компоненты внутри — flex, макет страницы и карточные сетки — grid.
Практика
Заголовок раздела «Практика»- Диагностика. Сверстай flex-строку из сайдбара 260px и контента. Положи в контент длинную непрерывную строку (без пробелов), добейся горизонтального скролла, затем почини через
min-width: 0. Критерий: можешь объяснить, почему помогло именно это свойство. - Формула grow. Три айтема
flex-basis: 150pxв контейнере 900px с коэффициентамиflex-grow: 1, 2, 3. Посчитай итоговые ширины на бумаге, сверь с DevTools → Computed → flex-basis/flex-grow. Критерий: совпадение до десятых пикселя. - Карточки. Сделай 3 карточки в ряд одинаковой высоты с кнопкой, прижатой к низу каждой карточки, используя вложенный column-флекс и
margin-top: auto. Критерий: при разном объёме текста кнопки стоят на одной линии. - Шапка. Собери шапку: лого слева, навигация по центру, аватар справа — только flex-свойства, без absolute и float. Критерий: центр навигации остаётся по центру при разной ширине лого и аватара.
- Sticky footer. Собери страницу header/main/footer, где футер всегда внизу при любом объёме контента (проверь и пустой main, и main на 5 экранов). Критерий: нет ни зазора под футером, ни наложений.
Если синтаксис ещё не сидит в пальцах — пройди Flexbox Froggy: 24 уровня, каждый закрепляет ровно одно свойство из этой главы. Первоисточник по терминологии — спецификация CSS Flexible Box Layout Module.
Что почитать
Заголовок раздела «Что почитать»- MDN: Flexbox — базовые концепции с интерактивными примерами.
- MDN: Контролирование соотношения элементов по главной оси — глубокий разбор grow/shrink/basis с алгоритмами.
- CSS Tricks: A Complete Guide to Flexbox — шпаргалка всех свойств с графиками.
- Every Layout: The Flexbox Layout — философия «содержимое определяет раскладку» против фиксированных брейкпоинтов.
- web.dev: Flexbox froggy как тренажёр — закрепление свойств игрой (пройди всю, это быстро).