Flexbox в CSS: выравнивание, перенос и размеры элементов

Веб Автор: Среда и версия: HTML и CSS; примеры и расчёты проверены статически 30 сентября 2026. Браузерная проверка не проводилась.

Название проекта вытесняет меню за край. Добавляешь flex-shrink: 1, но ничего не меняется. А после flex-direction: column знакомое justify-content: center вдруг начинает двигать элементы вверх и вниз.

Flexbox — способ расположить элементы вдоль одной главной оси и распределить между ними место. Чтобы управлять раскладкой, нужно различать три вещи: направление осей, перенос на новые линии и размеры самих элементов. Разберём их на шапке с названием проекта и навигацией.

Главная и поперечная оси: куда выравнивает Flexbox

display: flex делает элемент flex-контейнером, а его непосредственных потомков — flex-элементами. Вложенный абзац или ссылка внутри потомка не становится участником внешней раскладки.

Главную ось задаёт flex-direction. Поперечная проходит перпендикулярно ей. В обычном русском тексте с writing-mode: horizontal-tb и direction: ltr это выглядит так:

НаправлениеГлавная осьПоперечная ось
rowСлева направоСверху вниз
columnСверху внизСлева направо

justify-content распределяет свободное место по главной оси, а align-items выравнивает элементы по поперечной внутри каждой линии. Поэтому запоминать «justify — горизонталь» неудобно: после переключения на column правило перестанет работать.

Flexbox / 01

Ось меняется, назначение свойства остаётся

Главная и поперечная оси шапкиУсловия: горизонтальное письмо слева направо. Шаг 1: при row бренд, название и меню идут слева направо по главной оси, поперечная направлена вниз. Шаг 2: при column те же элементы идут сверху вниз по главной оси, поперечная направлена вправо. justify-content всегда относится к главной оси, align-items — к поперечной.01 / flex-direction: rowГлавная ось · justify-contentПоперечная ось направлена вниз · align-itemswebНазваниеalign-items: centerМеню02 / flex-direction: columnПоперечная ось · align-itemswebНазваниеМенюГлавная ось направлена внизjustify-contentЗдесь элементы выровненыпо началу поперечной оси:align-items: flex-startУсловия схемы: writing-mode: horizontal-tb; direction: ltr.
При row главная ось горизонтальна, при column — вертикальна в указанных условиях письма. Свойства выравнивания следуют за осями, а не за сторонами экрана.

При direction: rtl начало строки окажется справа; вертикальный writing-mode тоже меняет картину. row-reverse и column-reverse разворачивают соответствующее направление. Подробности — в объяснении осей Flexbox на MDN.

Разворачивать визуальный порядок ради адаптива стоит осторожно: DOM-порядок при этом сохраняется, и переход клавишей Tab может не совпасть с тем, что видит человек. В нашем примере перестановок нет.

В песочнице HTML и CSS замени содержимое index.html первым блоком, а styles.css — вторым. Получится шапка из трёх частей: короткое имя сайта, название проекта и меню. Ссылки ведут к разделам этого же примера.

<header class="site-header">
  <a class="brand" href="#about">web</a>
  <div class="heading">
    <p class="eyebrow">Личный проект</p>
    <p class="project-name">Библиотека материалов для первой самостоятельной страницы</p>
  </div>
  <nav class="nav" aria-label="Разделы страницы">
    <a href="#about">Описание</a>
    <a href="#practice">Практика</a>
  </nav>
</header>

<main>
  <h1 id="about">Библиотека материалов для первой самостоятельной страницы</h1>
  <p>Здесь собираются заметки о вёрстке и примеры кода.</p>
  <h2 id="practice">Практика</h2>
  <p>Измени ширину предпросмотра и посмотри, куда переместится меню.</p>
</main>
:root {
  --page: #15171a;
  --text: #f2f4f8;
  --border: #505967;
  --accent: #7cc4ff;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 1rem;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
  color: var(--text);
  background: var(--page);
}

.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  border: 1px solid var(--border);
}

.brand {
  flex: 0 0 auto;
  font-weight: 700;
}

.heading {
  flex: 1 1 18rem;
  min-width: 0;
}

.eyebrow,
.project-name {
  margin: 0;
}

.eyebrow {
  font-size: 0.875rem;
}

.project-name {
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  max-width: 100%;
}

.nav a {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  overflow-wrap: anywhere;
}

a {
  color: var(--accent);
  text-underline-offset: 0.2em;
}

a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

main {
  max-width: 68ch;
  overflow-wrap: anywhere;
}

Внешняя шапка раскладывает три элемента. Меню .nav создаёт свой flex-контейнер и управляет только двумя ссылками внутри. Свойства gap и flex-wrap у этих контейнеров независимы. В ссылках разрешён перенос через overflow-wrap: anywhere: даже длинное слово без пробелов сможет поместиться в узком меню.

Название в шапке сокращается многоточием, а ниже в h1 остаётся полным. Так короткая шапка не лишает читателя названия страницы. Внешних ресурсов и JavaScript в примере нет.

В песочнице результат меняется при вводе. Кнопка «Проверить» относится к исходному заданию на центрирование карточки; эту шапку исследуй в предпросмотре, включая переключение на мобильную ширину.

Перенос, gap и свободное место — разные задачи

Без flex-wrap: wrap контейнер держит элементы на одной линии: начальное значение — nowrap. Сжатие может помочь им поместиться, но само по себе не создаёт новую строку.

wrap разрешает дополнительные линии. В нашей шапке браузер собирает их с учётом размеров элементов, ограничений и промежутков. Затем каждая линия отдельно распределяет своё место. Перенос происходит до окончательного гибкого изменения размеров; это не правило «сначала ужать всё до предела, потом перенести».

СвойствоЗа что отвечаетЧто увидеть в примере
flex-wrap: wrapРазрешает несколько линийНа узкой ширине части шапки переходят ниже
gap: 1remЗадаёт промежутки между элементами и линиямиЧасти шапки не прилипают друг к другу
align-items: centerВыравнивает элементы поперёк своей линииМеню стоит по центру относительно блока из двух строк
justify-contentРаспределяет оставшееся место вдоль линииПосле роста .heading свободного остатка может не быть

В примере flex-grow у .heading забирает свободную ширину. Поэтому добавление justify-content: space-between к шапке может ничего не изменить: распределять уже нечего. Если нужно проверить это свойство, временно поставь .heading { flex-grow: 0; }.

gap не добавляет отступ по внешнему краю: для этого у шапки есть padding. А space-between может сделать расстояния больше заданного gap, когда свободное место осталось.

При переносе не путай два свойства: align-items выравнивает элементы внутри линий, align-content распределяет сами линии по поперечной оси. В нашей шапке высота определяется содержимым, поэтому отдельно распределять линии по высоте незачем. Механика переноса описана в справке MDN о flex-wrap.

flex-grow, flex-shrink и flex-basis: читать три числа

У названия проекта стоит сокращённая запись:

.heading {
  flex: 1 1 18rem;
}

Её порядок — рост, сжатие, базовый размер:

  • flex-grow: 1 разрешает получать долю свободного места. У .heading это единственный растущий элемент, поэтому на своей линии он забирает остаток.
  • flex-shrink: 1 разрешает уменьшаться, когда места не хватает. Минимальные размеры при этом продолжают действовать.
  • flex-basis: 18rem задаёт стартовый размер по главной оси. Это не обещание конечной ширины: результат меняют рост, сжатие и ограничения.

У короткого имени сайта flex: 0 0 auto: оно не растёт и не сжимается, а базовый размер зависит от содержимого, поскольку отдельная ширина не задана. Значения сокращения собраны в справке MDN о flex.

flex-grow: 2 не означает «элемент вдвое шире». Когда два элемента делят положительный остаток с коэффициентами 1 и 2, второй получает вдвое больше добавочного места. Их исходные размеры могут различаться.

Со сжатием есть ещё одна деталь. При одинаковом flex-shrink: 1 более крупная база отдаёт больше пикселей. Выделим отдельный расчёт для двух элементов «Название» и «Меню»: контейнер 500px, базы 200px и 400px, gap: 20px. Перенос выключен, отступов и границ нет, min-width: 0, верхние ограничения размера не заданы.

Не хватает 200 + 400 + 20 − 500 = 120px. Отношение баз — 1:2, значит элементы отдадут 40px и 80px. Итог: 160 + 320 + 20 = 500px. Это расчёт по алгоритму Flexbox, а не замер шапки выше.

Flexbox / 02

Одинаковый shrink не означает одинаковую потерю ширины

Как убрать нехватку 120 пикселейКонтейнер шириной 500 пикселей, промежуток 20 пикселей. Шаг 1: исходные базы названия и меню равны 200 и 400 пикселям, вместе с промежутком нужно 620. Шаг 2: у обоих flex-shrink 1, название отдаёт 40 пикселей, меню — 80. Итоговые размеры 160 и 320, с промежутком они занимают ровно 500 пикселей. Общий масштаб ширин — одна единица SVG на один пиксель расчёта.01 / исходные размеры200 + 400 + 20 = 620 pxЛишние 120 pxНазвание200 px · shrink: 1Меню400 px · shrink: 1Нехватка делится в отношении 200:400, то есть 1:2.02 / после сжатия160 + 320 + 20 = 500 pxНазвание160 px · −40 pxМеню320 px · −80 pxПунктир — крайконтейнера 500 px.gap: 20pxБез переноса, padding и border; min-width: 0; верхних ограничений нет.
Сжатие учитывает произведение flex-shrink × базовый размер. При равных коэффициентах блок с базой 400px отдаёт вдвое больше пикселей, чем блок с базой 200px. Промежуток остаётся 20px.

Почему длинному тексту нужен min-width: 0

Вернёмся к шапке. У .project-name стоит white-space: nowrap: название должно оставаться в одну строку. Родитель .heading участвует в Flexbox, и его начальный min-width: auto может удерживать минимум по содержимому. Неразрывная строка делает этот минимум слишком большим.

flex-shrink: 1 разрешает сжатие, но не отменяет минимальный размер. Поэтому мы явно ставим min-width: 0 на .heading — flex-элемент, который должен уступить место. На одном только вложенном абзаце это не снимет ограничение родителя. Поведение auto описано в справке MDN о min-width.

Дальше работают свойства самого текста:

СвойствоЗачем оно здесь
min-width: 0 у .headingРазрешает сузить flex-элемент ниже его автоматического минимума
white-space: nowrap у .project-nameСохраняет название одной строкой
overflow: hidden у .project-nameСкрывает не поместившуюся часть строки
text-overflow: ellipsis у .project-nameОбозначает скрытый хвост многоточием

Если по смыслу название надо показывать целиком прямо в шапке, замени три правила обрезки у .project-name на overflow-wrap: anywhere. Обычный текст будет переноситься по словам, длинное слово без пробелов тоже сможет разорваться. Не оставляй одновременно white-space: nowrap: он запрещает перенос, который ты пытаешься разрешить.

Не расставляй min-width: 0 всем элементам наугад. Сначала найди flex-элемент, чей минимум мешает сжатию. Для вертикальной раскладки похожую роль иногда играет min-height: 0.

Когда вместо Flexbox нужен Grid

У нашей шапки элементы разной ширины, а меню может перейти на другую линию. Flexbox подходит: каждая линия распределяет своё место независимо.

У каталога другое требование: карточки второго ряда должны стоять под колонками первого. Для общей системы строк и столбцов удобнее Grid. Это разобрано на адаптивной сетке карточек CSS Grid. В одной странице оба способа могут работать вместе: Grid снаружи карточек, Flexbox внутри каждой.

Четыре изменения, которые помогут разобраться

Перед каждой правкой возвращай исходный CSS и сначала предсказывай результат:

  1. Убери min-width: 0 у .heading. На узкой ширине проверь, мешает ли полное название сжать этот блок. Верни правило и сравни.
  2. Замени flex-wrap: wrap на nowrap у .site-header. Части шапки останутся на одной линии, а названию достанется меньше места. Настройка .nav при этом не меняется: ссылки внутри неё всё ещё могут переноситься.
  3. Поставь .heading { flex-grow: 0; } и добавь шапке justify-content: space-between. На широкой строке появится свободное место для распределения между элементами. Затем верни рост и посмотри, кто теперь использует этот остаток.
  4. Замени текст ссылки «Практика» на СамостоятельныеЭкспериментыСВёрсткой. На узкой ширине слово должно переноситься внутри ссылки благодаря overflow-wrap: anywhere. В отличие от названия в шапке, текст меню остаётся виден целиком.

Если после этих опытов хочется потренировать выбор свойств без оформления страницы, открой игру по Flexbox и Grid. Здесь же попробуй сохранить название целиком: разреши перенос текста и объясни, какие правила обрезки стали не нужны.

Источники