На широком экране три карточки стоят в ряд. На телефоне третья уезжает за край, а длинное название раздвигает соседей. Знакомый результат после width: 33.33% и пары отступов.
CSS Grid позволяет задать правило для всей сетки: сколько карточек помещается, столько колонок и будет. Соберём каталог из трёх карточек, разберём одну строку CSS и проверим места, где такая раскладка обычно ломается. Для примера хватит HTML и CSS; JavaScript не понадобится.
Адаптивная сетка CSS Grid: готовый пример
Открой песочницу HTML и CSS. Замени содержимое вкладки index.html первым блоком, а styles.css — вторым. Картинки, шрифты и библиотеки загружать не нужно.
HTML — три карточки внутри одного контейнера:
<main class="catalog">
<h1>Что изучить дальше</h1>
<div class="cards">
<article class="card">
<h2>HTML</h2>
<p>Собери страницу с заголовками, списком и формой.</p>
</article>
<article class="card">
<h2>CSS</h2>
<p>Настрой отступы и преврати список в сетку карточек.</p>
</article>
<article class="card">
<h2>JavaScript</h2>
<p>Добавь фильтр, который оставляет нужные карточки.</p>
</article>
</div>
</main>
CSS — раскладка и немного оформления:
:root {
--page: #15171a;
--surface: #22262d;
--text: #f2f4f8;
--border: #505967;
--accent: #7cc4ff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 1rem;
font-family: system-ui, sans-serif;
line-height: 1.6;
color: var(--text);
background: var(--page);
}
.catalog {
max-width: 72rem;
margin-inline: auto;
overflow-wrap: anywhere;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1rem;
}
.card {
padding: 1.25rem;
border: 1px solid var(--border);
border-radius: 0.75rem;
background: var(--surface);
}
.card h2 {
margin: 0 0 0.5rem;
color: var(--accent);
}
.card p {
margin: 0;
}
Результат обновляется при вводе. В песочнице можно переключить предпросмотр на мобильную ширину. Её кнопка «Проверить» относится к исходному заданию про центрирование карточки; этот пример изучай в предпросмотре.
У Grid здесь две роли. .cards — контейнер сетки; три непосредственных потомка .card — элементы сетки. Заголовки и абзацы внутри карточек сами по себе в общую сетку не попадают.
Как работают repeat, minmax и 1fr
За число колонок отвечает одна декларация:
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
Читать её удобнее изнутри наружу.
| Фрагмент | Что он означает в этом примере |
|---|---|
min(100%, 16rem) | Минимум колонки: меньшее из ширины контейнера и 16rem |
minmax(…, 1fr) | Колонка может вырасти от этого минимума, получив долю свободного места |
repeat(auto-fit, …) | Создать столько колонок, сколько помещается, и схлопнуть оставшиеся пустыми |
gap: 1rem | Оставить промежуток между соседними колонками и рядами |
fr — доля доступного пространства. В нашей сетке у всех колонок одинаковый минимум и одинаковое 1fr, поэтому занятые колонки получают равную ширину. Сначала учитываются промежутки gap, затем распределяется место для колонок.
При корневом размере шрифта 16px наши 16rem равны 256px, а 1rem — 16px. Двум колонкам потребуется 256 × 2 + 16 = 528px. Трём — 256 × 3 + 16 × 2 = 800px.
Считать нужно ширину самого .cards, а не окна браузера. Боковые отступы страницы уже забрали часть места. Например, при ширине области просмотра 832px, без вертикальной полосы прокрутки и при 1rem = 16px, после двух отступов body сетке останется 800px.
Один CSS, три ширины контейнера
16rem вложенный min() разрешает сузить единственную колонку.Вот расчёт для трёх карточек. Это значения из CSS примера, а не замеры устройств:
Внутренняя ширина .cards | Колонок | Ширина одной | Куда попадёт третья карточка |
|---|---|---|---|
240px | 1 | 240px | На третий ряд |
528px | 2 | 256px | На второй ряд |
800px | 3 | 256px | В третий столбец первого ряда |
Если изменить корневой размер шрифта или gap, пороги перестроения тоже изменятся. Это следствие выбранных размеров, а не привязка к конкретному телефону.
auto-fit и auto-fill: разница видна на пустом месте
Оставь две карточки и сделай контейнер достаточно широким для трёх колонок. Теперь замени auto-fit на auto-fill.
auto-fitсхлопывает пустую колонку. Две оставшиеся благодаря1frзанимают освободившееся место.auto-fillсохраняет пустую колонку. Две карточки остаются в первых двух ячейках, справа есть свободная третья.
Такое поведение задано в спецификации CSS Grid. Различие относится именно к пустым колонкам, а не к числу карточек в последнем ряду.
Две карточки в контейнере шириной 800 px
auto-fit две карточки делят всю доступную ширину. При auto-fill место для третьей колонки остаётся, даже если третьей карточки нет. Здесь 1rem = 16px.Есть ловушка: если карточек пять, а колонок три, две карточки последнего ряда не растянутся на всю строку даже с auto-fit. Все три колонки заняты карточками первого ряда; схлопывать нечего. Сетка сохраняет общие столбцы.
Для нашего каталога оставим auto-fit: если карточка всего одна, пусть занимает доступную ширину. Если дизайн требует сохранить место под соседей, пригодится auto-fill.
Почему сетка вылезает за край
Сначала найди, что именно не помещается: колонка, содержимое карточки или внешний контейнер. Это разные причины.
| Симптом | Что проверить | Что изменить |
|---|---|---|
На узкой ширине карточка не сжимается ниже 16rem | Не осталось ли minmax(16rem, 1fr) | Добавить вложенный min(100%, 16rem) |
| Длинное слово выступает за рамку | Есть ли у текста точки переноса | Задать overflow-wrap: anywhere |
| Карточка шире своей колонки | Нет ли width: 400px или min-width | Убрать ненужное ограничение; для автоматического минимума по содержимому проверить min-width: 0 |
| Картинка выступает за край | Не сохраняет ли она исходную ширину | Задать max-width: 100%; height: auto |
В примере overflow-wrap: anywhere задан на .catalog и наследуется текстом внутри. Обычные фразы по-прежнему переносятся по словам. Если отдельное слово или длинный идентификатор не помещается, браузер получает право разорвать его внутри. Подробности — в справке MDN об overflow-wrap.
min-width: 0 и overflow-wrap решают разные задачи. Первое позволяет элементу сжаться при ограничивающем автоматическом минимуме, второе помогает тексту поместиться внутри. В нашей сетке минимум колонок уже указан явно; добавлять min-width: 0 каждой карточке без причины не нужно.
Не прячь такую ошибку через overflow-x: hidden на странице. Полоса прокрутки исчезнет, но часть карточки может остаться обрезанной.
CSS Grid или Flexbox для карточек
Выбор зависит от того, должны ли элементы разных рядов стоять в общих колонках. MDN сравнивает Grid и Flexbox именно через управление одной или двумя осями.
| Задача | С чего начать |
|---|---|
| Карточки в ровных столбцах, включая неполный последний ряд | Grid |
| Ряд кнопок с переносом, где каждая строка распределяет место отдельно | Flexbox |
| Каталог с кнопкой внизу каждой карточки | Grid для каталога; внутри карточки может пригодиться Flexbox |
Flexbox тоже умеет переносить элементы. Разница в том, что каждая его строка распределяет свободное место независимо. Поэтому требование «последний ряд растягивается целиком» — повод посмотреть на перенос и распределение места во Flexbox, а не усложнять Grid.
Проверь себя на трёх изменениях
Верни исходные HTML и CSS, а затем меняй по одному условию:
- Длинное название. Замени
JavaScriptнаОченьДлинноеНазваниеБезЕдиногоПробела. Оно должно переноситься внутри карточки. Затем временно убериoverflow-wrapи сравни результат на узкой ширине. - Одна карточка. Удали две карточки. С
auto-fitоставшаяся займёт ширину контейнера; сauto-fillна широком экране справа останется место под пустые колонки. - Пять карточек. Добавь две копии. При ширине
.cardsв800pxи1rem = 16pxполучится три карточки сверху и две снизу. Нижние сохранят ширину верхних.
Перед добавлением медиазапроса проверь, нельзя ли выразить задачу размером колонки и промежутком. Для этой сетки правило уже записано: карточка стремится быть не уже 16rem, но уступает узкому контейнеру.
Когда станет понятно, почему карточки переезжают между рядами, попробуй игру по Flexbox и Grid. Там нужно самому подбирать свойства раскладки. А этот пример оставь в песочнице HTML и CSS: добавь карточки, измени gap и предскажи число колонок до просмотра результата.
Короткие ответы
Можно ли сделать адаптивную сетку без медиазапросов?
Да. В этом примере repeat(auto-fit, …) меняет число колонок по ширине контейнера. Медиазапрос пригодится, если на конкретной ширине нужно менять сам дизайн: например, расположение меню или состав блоков.
Почему gap не добавляет отступ по краям?
Он задаёт промежутки между рядами и колонками. Внешнее пространство нашего примера создаёт padding у body. Если отступ нужен внутри рамки отдельной сетки, задай padding её контейнеру.
Все карточки будут одинаковой высоты?
В нашем примере карточки растягиваются по высоте своего ряда. Сам ряд подстраивается под самую высокую карточку в нём. Разные ряды могут иметь разную высоту: фиксированную высоту мы не задавали.
Почему 1fr не всегда равно третьей части ширины?
Число колонок может измениться, а промежутки тоже занимают место. В нашей сетке из трёх одинаковых колонок с двумя промежутками по 16px каждая получает (ширина контейнера − 32px) / 3.