CSS Grid: адаптивная сетка карточек на одном примере

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

На широком экране три карточки стоят в ряд. На телефоне третья уезжает за край, а длинное название раздвигает соседей. Знакомый результат после 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 Grid / 01

Один CSS, три ширины контейнера

Как три карточки переходят из трёх колонок в однуПри минимуме 256 пикселей и промежутке 16 пикселей контейнер 800 пикселей вмещает три колонки. В контейнере 528 пикселей две колонки, JavaScript переходит на второй ряд. В контейнере 240 пикселей одна колонка шириной 240 пикселей, все три карточки идут друг под другом. Ширины показаны в одном масштабе.01 / контейнер 800 pxТри колонки по 256 pxHTMLCSSJavaScript02 / контейнер 528 pxДве колонки по 256 pxHTMLCSSJavaScriptТретья карточкапереходит на новый ряд.03 / контейнер 240 pxОдна колонка, минимум снижается до 240 pxHTMLCSSJavaScriptmin(100%, 16rem)Позволяет колонке стать уже 256 px,когда контейнер сам уже 256 px.В расчёте: 1rem = 16px, gap = 16px.
Колонки перестраиваются по доступной ширине контейнера. Для этих переходов не нужны отдельные медиазапросы; на ширине меньше 16rem вложенный min() разрешает сузить единственную колонку.

Вот расчёт для трёх карточек. Это значения из CSS примера, а не замеры устройств:

Внутренняя ширина .cardsКолонокШирина однойКуда попадёт третья карточка
240px1240pxНа третий ряд
528px2256pxНа второй ряд
800px3256pxВ третий столбец первого ряда

Если изменить корневой размер шрифта или gap, пороги перестроения тоже изменятся. Это следствие выбранных размеров, а не привязка к конкретному телефону.

auto-fit и auto-fill: разница видна на пустом месте

Оставь две карточки и сделай контейнер достаточно широким для трёх колонок. Теперь замени auto-fit на auto-fill.

  • auto-fit схлопывает пустую колонку. Две оставшиеся благодаря 1fr занимают освободившееся место.
  • auto-fill сохраняет пустую колонку. Две карточки остаются в первых двух ячейках, справа есть свободная третья.

Такое поведение задано в спецификации CSS Grid. Различие относится именно к пустым колонкам, а не к числу карточек в последнем ряду.

CSS Grid / 02

Две карточки в контейнере шириной 800 px

auto-fit расширяет занятые колонки, auto-fill сохраняет пустуюМинимум колонки 256 пикселей, промежуток 16 пикселей. В режиме auto-fit две карточки HTML и CSS занимают по 392 пикселя. В режиме auto-fill сохраняются три колонки по 256 пикселей: HTML, CSS и пустая колонка. Контейнеры показаны в одинаковом масштабе.01 / auto-fitПустая колонка схлопнуласьHTML392 pxCSS392 px02 / auto-fillПустая колонка сохраниласьHTML256 pxCSS256 pxПусто256 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, а затем меняй по одному условию:

  1. Длинное название. Замени JavaScript на ОченьДлинноеНазваниеБезЕдиногоПробела. Оно должно переноситься внутри карточки. Затем временно убери overflow-wrap и сравни результат на узкой ширине.
  2. Одна карточка. Удали две карточки. С auto-fit оставшаяся займёт ширину контейнера; с auto-fill на широком экране справа останется место под пустые колонки.
  3. Пять карточек. Добавь две копии. При ширине .cards в 800px и 1rem = 16px получится три карточки сверху и две снизу. Нижние сохранят ширину верхних.

Перед добавлением медиазапроса проверь, нельзя ли выразить задачу размером колонки и промежутком. Для этой сетки правило уже записано: карточка стремится быть не уже 16rem, но уступает узкому контейнеру.

Когда станет понятно, почему карточки переезжают между рядами, попробуй игру по Flexbox и Grid. Там нужно самому подбирать свойства раскладки. А этот пример оставь в песочнице HTML и CSS: добавь карточки, измени gap и предскажи число колонок до просмотра результата.

Короткие ответы

Можно ли сделать адаптивную сетку без медиазапросов?

Да. В этом примере repeat(auto-fit, …) меняет число колонок по ширине контейнера. Медиазапрос пригодится, если на конкретной ширине нужно менять сам дизайн: например, расположение меню или состав блоков.

Почему gap не добавляет отступ по краям?

Он задаёт промежутки между рядами и колонками. Внешнее пространство нашего примера создаёт padding у body. Если отступ нужен внутри рамки отдельной сетки, задай padding её контейнеру.

Все карточки будут одинаковой высоты?

В нашем примере карточки растягиваются по высоте своего ряда. Сам ряд подстраивается под самую высокую карточку в нём. Разные ряды могут иметь разную высоту: фиксированную высоту мы не задавали.

Почему 1fr не всегда равно третьей части ширины?

Число колонок может измениться, а промежутки тоже занимают место. В нашей сетке из трёх одинаковых колонок с двумя промежутками по 16px каждая получает (ширина контейнера − 32px) / 3.

Источники