CSS Grid и Flexbox: как выбирать и строить адаптивные макеты
Когда использовать Flexbox, а когда Grid: основы и паттерны, именованные области, адаптивная сетка без медиазапросов, subgrid, контейнерные запросы, доступность визуального порядка, производительность и отладка вёрстки.
Ещё несколько лет назад вёрстка сложной сетки означала обтекание элементов, отрицательные отступы, фиксированные ширины и хрупкие хаки для выравнивания по вертикали. Сегодня Flexbox и CSS Grid поддерживаются всеми современными браузерами и позволяют описывать макет прямо: какие области есть на странице, как они делят пространство и как перестраиваются на разных экранах. Но возможности не гарантируют хорошего результата. Частая картина в реальных проектах — Grid там, где хватило бы одной строки Flexbox, вложенность из пяти flex-контейнеров вместо одной сетки, сотни медиазапросов, которые можно было заменить одной формулой, и порядок элементов на экране, который расходится с порядком для программ чтения с экрана. Ниже — как выбирать между Flexbox и Grid, базовые и продвинутые приёмы, адаптивные сетки без медиазапросов, контейнерные запросы и доступность. Главное различие: одно измерение или два Flexbox распределяет элементы вдоль одной оси — строки или столбца. Размер элементов часто определяется их содержимым, а контейнер решает, как распределить оставшееся пространство. Grid описывает двумерную сетку: строки и столбцы одновременно. Макет задаётся контейнером, а элементы размещаются в его ячейках и областях. Практическое правило: если вы думаете «эти элементы должны идти в ряд и красиво распределиться», — это Flexbox. Если думаете «на странице есть шапка, боковая панель, контент и подвал, и они должны быть выровнены между собой», — это Grid. Во многих интерфейсах используются оба: Grid для структуры страницы или карточек, Flexbox внутри элементов. Flexbox Основы .toolbar { display: flex; align-items: center; /* выравнивание по поперечной оси */ justify-content: space-between; /* распределение по главной оси */ gap: 12px; /* отступы между элементами */ } Свойство gap работает во Flexbox во всех современных браузерах и заменяет отступы у дочерних элементов с последующим обнулением у последнего. Как элементы делят пространство Сокращённое свойство flex задаёт три значения: способность расти, способность сжиматься и базовый размер. .sidebar { flex: 0 0 280px; } /* не растёт, не сжимается, 280px */ .content { flex: 1 1 0; min-width: 0; } /* занимает всё остальное */ Обратите внимание на min-width: 0 . По умолчанию flex-элемент не сжимается меньше размера своего содержимого. Длинная строка без пробелов, таблица или блок кода внутри растягивают элемент и ломают макет. Это одна из самых частых причин «почему у меня всё уехало вправо». Типовые паттерны /* Центрирование по обеим осям */ .center { display: flex; align-items: center; justify-content: center; } /* Прижать последний элемент вправо */ .nav { display: flex; gap: 16px; } .nav .actions { margin-left: auto; } /* Карточка: кнопка всегда внизу независимо от длины текста */ .card { display: flex; flex-direction: column; } .card .body { flex: 1; } /* Ряд, который переносится на следующую строку */ .tags { display: flex; flex-wrap: wrap; gap: 8px; } CSS Grid Основы .layout { display: grid; grid-template-columns: 240px 1fr; grid-template-rows: auto 1fr auto; gap: 24px; min-height: 100dvh; } Единица fr означает долю свободного пространства после размещения фиксированных колонок. Единица dvh учитывает динамическую высоту окна на мобильных устройствах, где адресная строка браузера появляется и скрывается. Именованные области Самый наглядный способ описать макет страницы. .page { display: grid; grid-template-columns: 260px 1fr; grid-template-areas: "header header" "sidebar main" "footer footer"; } .page header { grid-area: header; } .page aside { grid-area: sidebar; } .page main { grid-area: main; } .page footer { grid-area: footer; } @media (max-width: 768px) { .page { grid-template-columns: 1fr; grid-template-areas: "header" "main" "sidebar" "footer"; } } Перестройка макета на мобильном меняет только описание областей в одном месте, а не стили каждого элемента. Размещение по линиям .featured { grid-column: 1 / -1; /* от первой линии до последней: вся ширина */ } .wide { grid-column: span 2; /* занять две колонки */ } Функция minmax .table-like { display: grid; grid-template-columns: minmax(160px, 2fr) minmax(100px, 1fr) 120px; } minmax задаёт колонке минимальный и максимальный размер — колонка растёт, но не сжимается ниже заданного предела. Адаптивная сетка без медиазапросов Одна строка создаёт сетку карточек, которая сама подбирает число колонок под ширину контейнера. .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); gap: 20px; } minmax(280px, 1fr) — колонка не уже 280px и растягивается на доступное место; min(280px, 100%) — на экранах уже 280px колонка сжимается до ширины контейнера и не вызывает горизонтальную прокрутку; auto-fill создаёт столько колонок, сколько помещается, сохраняя пустые колонки, если элементов мало; auto-fit схлопывает пустые колонки, и элементы растягиваются на всю ширину. Разница между auto-fill и auto-fit видна, только когда элементов меньше, чем помещается колонок: с auto-fit две карточки растянутся на всю ширину, с auto-fill останутся шириной в колонку. Плавные размеры через clamp .container { width: min(100% - 32px, 1200px); margin-inline: auto; } h1 { font-size: clamp(1.75rem, 1.2rem + 2.5vw, 3rem); } clamp задаёт минимум, предпочтительное значение и максимум. Размеры меняются плавно, без скачков на границах медиазапросов. Subgrid: выравнивание вложенных элементов Классическая проблема: в ряду карточек заголовки разной длины, и описания начинаются на разной высоте. Subgrid позволяет дочерним элементам использовать строки родительской сетки. .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 20px; } .card { display: grid; grid-row: span 3; grid-template-rows: subgrid; /* заголовок, текст, кнопка выровнены по всем карточкам */ } Subgrid поддерживается всеми основными современными браузерами. Для старых версий макет остаётся рабочим, просто без выравнивания по строкам. Контейнерные запросы Медиазапросы реагируют на ширину окна, но компонент может оказаться и в широкой основной колонке, и в узкой боковой панели. Контейнерные запросы позволяют компоненту адаптироваться к ширине своего контейнера. .product-list { container-type: inline-size; } .product { display: grid; gap: 12px; } @container (min-width: 520px) { .product { grid-template-columns: 160px 1fr; } } Это делает компоненты по-настоящему переиспользуемыми: одна и та же карточка товара правильно выглядит в каталоге, в корзине и в боковом блоке рекомендаций. Контейнерные запросы поддерживаются всеми современными браузерами. Выравнивание: одна система для обоих Свойства выравнивания работают и во Flexbox, и в Grid, с небольшими различиями. justify-content и align-content распределяют дорожки или строки в контейнере; align-items и justify-items выравнивают элементы внутри их ячеек или строки; align-self и justify-self — выравнивание отдельного элемента; place-items: center в Grid — центрирование в одну строку. Доступность: визуальный порядок и порядок в документе Свойство order , размещение по областям Grid и flex-direction: row-reverse меняют визуальный порядок, но не порядок в документе. Программы чтения с экрана и навигация с клавиатуры следуют порядку в HTML. Если визуально кнопка «Купить» стоит первой, а в разметке последней, пользователь клавиатуры попадёт на неё в конце — и порядок фокуса будет казаться случайным. располагайте элементы в HTML в логическом порядке и меняйте визуальный порядок только когда он не противоречит смыслу; проверяйте навигацию клавишей Tab после перестройки макета на разных экранах; не используйте Grid для имитации таблиц с табличными данными — для них есть элемент table , семантику которого понимают вспомогательные технологии. Подробно о доступности интерфейсов — в статье об Accessibility , а о проектировании от маленьких экранов — в материале о Mobile-First . Производительность вёрстки сдвиги макета при загрузке ухудшают метрику CLS: резервируйте место под изображения через атрибуты width и height или aspect-ratio , под динамические блоки — через минимальную высоту; анимируйте transform и opacity , а не размеры и положение в сетке, которые вызывают пересчёт макета; для очень длинных списков используйте content-visibility: auto или виртуализацию, а не тысячи элементов в одной сетке; глубоко вложенные flex-контейнеры, каждый из которых зависит от размеров содержимого, заставляют браузер пересчитывать макет многократно — плоская сетка обычно быстрее и проще. Общие приёмы ускорения интерфейсов — в статье о performance optimization . Отладка Инструменты разработчика в Chrome, Firefox и Safari умеют показывать линии сетки, номера и имена линий, области и промежутки прямо поверх страницы, а также подсвечивать flex-контейнеры и распределение пространства. Когда элемент «не туда встал», наложение сетки в инструментах разработчика почти всегда быстрее, чем перебор свойств. Частые причины проблем: flex-элемент не сжимается — забыт min-width: 0 или overflow: hidden ; колонка 1fr растягивается длинным содержимым — используйте minmax(0, 1fr) ; элемент выпадает из области — опечатка в имени grid-area создаёт неявную строку; неожиданные дополнительные строки — элементов больше, чем явно описано в сетке, и Grid создаёт неявные дорожки; их размер задаётся свойством grid-auto-rows . Grid или Flexbox: краткая шпаргалка Задача Инструмент Меню, панель инструментов, кнопки в ряд Flexbox Центрирование одного элемента Grid ( place-items ) или Flexbox Структура страницы с областями Grid с grid-template-areas Сетка карточек, галерея Grid с auto-fill и minmax Выровнять части карточек между собой Grid с subgrid Теги и чипы с переносом Flexbox с flex-wrap Компонент, зависящий от ширины места Контейнерные запросы Форма: подписи и поля в две колонки Grid Адаптивные интерфейсы и дизайн-системы мы реализуем в рамках разработки веб-проектов . Частые вопросы Можно ли использовать только Grid или только Flexbox? Технически да, но это усложняет код. Grid удобнее для двумерных макетов, Flexbox — для одномерных рядов с распределением пространства. В реальных интерфейсах они естественно дополняют друг друга. Нужны ли ещё CSS-фреймворки с сеткой? Готовые классы сетки из старых фреймворков в современных проектах в основном избыточны: Grid решает ту же задачу нативно и гибче. Утилитарные фреймворки остаются удобны как способ быстро применять те же свойства Grid и Flexbox прямо в разметке. Почему элемент во Flexbox вылезает за пределы контейнера? Чаще всего потому, что минимальный размер flex-элемента по умолчанию равен размеру его содержимого. Добавьте min-width: 0 элементу, а длинным словам и ссылкам — overflow-wrap: anywhere . Медиазапросы или контейнерные запросы? Медиазапросы — для макета страницы в целом и для настроек пользователя вроде уменьшения анимации и тёмной темы. Контейнерные запросы — для компонентов, которые должны правильно выглядеть в местах разной ширины. Как поддерживать старые браузеры? Flexbox и базовый Grid поддерживаются повсеместно. Для новых возможностей — subgrid, контейнерных запросов — используйте проверку @supports и проектируйте так, чтобы без них макет оставался рабочим, пусть и менее точным. Что такое неявная сетка? Если элементов больше, чем ячеек в описанной сетке, Grid автоматически создаёт дополнительные строки или колонки. Их размер по умолчанию зависит от содержимого, а управлять им можно свойствами grid-auto-rows , grid-auto-columns и grid-auto-flow .