CSS Grid: верстка сетки через grid-template, fr, gap и области

CSS Grid: верстка сетки через grid-template, fr, gap и области Полезное

CSS Grid Layout — это модуль CSS для двумерной раскладки: элемент с display: grid делит свое пространство на строки и столбцы одновременно, а его прямые потомки занимают ячейки или области этой сетки. Ниже — рабочий макет страницы, единица fr, адаптивная сетка без медиазапросов, области, выравнивание, отличие от Flexbox и subgrid.

Все размеры в примерах — из прогона в Google Chrome 153 (headless) 23 сентября 2026 года: координаты элементов снимались через getBoundingClientRect().

Словарь: пять терминов, которые путают

Термин Что это
Контейнер Элемент с display: grid (или inline-grid)
Элемент сетки Только ПРЯМОЙ потомок контейнера; внуки в сетке не участвуют
Линия Граница между треками; у 3 столбцов 4 вертикальные линии, нумерация с 1
Трек Строка или столбец целиком
Область Прямоугольник из соседних ячеек, в том числе именованный

Сетка, заданная свойствами grid-template-*, называется явной. Если элементов больше, чем ячеек, браузер достраивает неявные треки, их размер задают grid-auto-rows и grid-auto-columns.

Минимальный полный пример: макет страницы

Сохраните как index.html и откройте в браузере:

<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<style>
  body { margin: 0; }
  .page {
    display: grid;
    grid-template-columns: 200px 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      "header  header"
      "sidebar main"
      "footer  footer";
    gap: 16px;
    min-height: 100vh;
  }
  .header  { grid-area: header; }
  .sidebar { grid-area: sidebar; }
  .main    { grid-area: main; }
  .footer  { grid-area: footer; }
  .page > * { background: #eef; padding: 12px; }

  @media (max-width: 600px) {
    .page {
      grid-template-columns: 1fr;
      grid-template-areas: "header" "main" "sidebar" "footer";
    }
  }
</style>
</head>
<body>
  <div class="page">
    <header class="header">Шапка</header>
    <nav class="sidebar">Меню</nav>
    <main class="main">Контент</main>
    <footer class="footer">Подвал</footer>
  </div>
</body>
</html>

При ширине окна 1000px меню получает ровно 200px, контент — 784px (1000 — 200 — 16 на зазор), шапка и подвал тянутся на всю ширину, а средняя строка 1fr забирает всю высоту, оставшуюся от шапки и подвала. При ширине 500px сетка становится одной колонкой, и контент встает выше меню.

Разбор по строкам:

  • grid-template-columns и grid-template-rows задают размеры треков через пробел.
  • grid-template-areas рисует карту областей строками в кавычках, а grid-area у элемента ставит его в область с этим именем.
  • gap — зазор между треками, у краев контейнера его нет.

Важная граница: сетка меняет только визуальный порядок. Клавиатура и скринридер идут по порядку в HTML, поэтому сильно перемешивать блоки относительно разметки не стоит.

Столбцы и единица fr

Задача: три колонки, первая — треть ширины, вторая — 100px, третья — остаток. Решение «в процентах» ломается. В контейнере шириной 600px:

.grid { grid-template-columns: 33.33% 100px 66.67%; }

Результат прогона: колонки 200 + 100 + 400 = 700px, третья вылезает за контейнер на 100px: проценты не учитывают соседние фиксированные треки.

Исправление — fr (fraction, доля свободного места):

.grid { grid-template-columns: 1fr 100px 2fr; }

Теперь 167 + 100 + 333 = 600px. Браузер сначала вычитает фиксированные треки и зазоры, а остаток делит по долям: (600 — 100) / 3 на одну долю. С gap: 20px на доли остается 460px, и колонки становятся 153, 100 и 307px.

Повторы сокращает repeat(): repeat(3, 1fr) — то же, что 1fr 1fr 1fr. Функцию можно смешивать с обычными значениями: grid-template-rows: 50px repeat(2, 2fr).

Ловушка: 1fr не всегда меньше контейнера

1fr по спецификации означает minmax(auto, 1fr): колонка не сжимается уже самого длинного неразрывного содержимого. Длинное слово или URL без пробелов и дефисов в колонке 1fr 1fr контейнера 600px дало в прогоне колонки 852 и 8px — сетка вылезла за контейнер.

.grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.grid > * { overflow-wrap: anywhere; }

С minmax(0, 1fr) колонки снова по 300px, а overflow-wrap: anywhere переносит длинную строку внутри ячейки вместо вылезания текста за ее край.

Адаптивные карточки без медиазапросов

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
}

Браузер помещает столько колонок не уже 200px, сколько влезает, и растягивает их на остаток. В контейнере 1000px получилось 4 колонки по 238px, в контейнере 500px — 2 колонки по 242px.

auto-fill и auto-fit отличаются, только когда элементов меньше, чем помещается колонок. С двумя карточками в 1000px auto-fill сохраняет пустые треки (карточки по 238px слева), а auto-fit схлопывает их, и две карточки растягиваются по 492px.

Размещение по линиям

Элемент можно поставить на конкретные линии, остальные браузер разложит в свободные ячейки сам:

.grid { display: grid; grid-template-columns: repeat(4, 100px); grid-auto-rows: 40px; }
.a { grid-column: 1 / 3; }              /* с 1-й по 3-ю линию: 2 колонки */
.b { grid-column: span 2; grid-row: 2; } /* 2 колонки во второй строке */
.c { grid-column: 1 / -1; }             /* от первой до последней линии */

Прогон с порядком A, B, D, C: A занял колонки 1-2 первой строки, D без правил встал в третью колонку рядом, B — во вторую строку, C растянулся на все 400px в третьей. Записи grid-column: 1 / 3 и пара grid-column-start: 1; grid-column-end: 3 равнозначны, отрицательные номера считают линии с конца явной сетки.

Области и их правила

Карта grid-template-areas строится по трем правилам: в каждой строке одинаковое число ячеек, точка . — пустая ячейка, область с одним именем обязана быть прямоугольником. Нарушение не дает «кривую» область — браузер отбрасывает объявление целиком:

.bad { grid-template-areas: "x y" "y x"; }

В прогоне getComputedStyle() вернул для grid-template-areas значение none, а элемент с grid-area: y уехал в неявные треки за край сетки. Если разметка «развалилась», первым делом проверьте прямоугольность областей и число ячеек в строках.

Свойство grid-template объединяет строки, столбцы и области в одну запись: grid-template: auto 1fr / 200px 1fr — сначала строки, после слеша столбцы.

Отступы: gap

gap: 10px 30px задает сначала зазор между строками, потом между столбцами — прогон подтвердил row-gap=10px, column-gap=30px. Одно значение ставит оба. Старое имя grid-gap браузеры принимают как синоним, в новом коде пишут gap.

Выравнивание

Свойство Где задают Что двигает
justify-items / align-items Контейнер Все элементы внутри своих ячеек, по горизонтали / вертикали
justify-self / align-self Элемент Один элемент внутри своей ячейки
justify-content / align-content Контейнер Всю сетку треков внутри контейнера

*-content работает, только если треки в сумме меньше контейнера. Проверка: контейнер 600×200, треки 2 по 100px и 2 по 50px, justify-content: center; align-content: end; align-items: center. Первая ячейка началась с x = 200 и y = 100 от края контейнера, а элемент высотой 20px встал по центру строки в 50px. Значение по умолчанию у *-items — stretch: элемент растянут на всю ячейку. Сокращения place-items и place-content пишут оба значения сразу.

Grid или Flexbox

Критерий Grid Flexbox
Оси Две: строки и столбцы связаны Одна главная ось; при flex-wrap строки выравниваются независимо
От чего идет раскладка От сетки: треки задают, элементы в них ставят От содержимого: размер элементов определяет раскладку
Типовые задачи Макет страницы, галерея, карточки с выровненными колонками Меню, панель кнопок, центрирование, строка из иконки и текста

Правило выбора: если нужно, чтобы элементы совпадали и по строкам, и по столбцам — Grid. Если элементы идут потоком в одну линию — Flexbox. Их часто совмещают: сетка страницы на Grid, содержимое шапки на Flexbox.

Вложенная сетка и subgrid

Элемент сетки сам может стать контейнером (display: grid), но его сетка независима от родительской. Отсюда частая проблема: в соседних карточках заголовки разной высоты, и кнопки «Подробнее» стоят вразнобой. subgrid заставляет вложенную сетку взять треки родителя:

.cards { display: grid; grid-template-columns: repeat(3, 200px); gap: 16px; }
.card  { display: grid; grid-row: span 3; grid-template-rows: subgrid; gap: 8px; }

Каждая карточка <article> с тремя детьми (<h3>, <p>, ссылка-кнопка) занимает три строки родителя и делит их с соседями. В прогоне кнопки всех трех карточек встали на одну высоту. В варианте без subgrid (у .card только display: grid; gap: 8px; align-content: start) кнопки разошлись примерно на 20px — точный сдвиг зависит от шрифта. Условие работы: элемент должен сам охватывать нужное число треков родителя (span 3), иначе подсетке нечего наследовать.

Поддержка: subgrid есть в актуальных Chrome, Edge, Firefox и Safari, в Chromium он появился позже всех, в версии 117 (2023 год).

Если не получилось

Симптом Причина Что сделать
Свойства сетки не действуют на блок Он не прямой потомок контейнера Сделать родителя контейнером или использовать subgrid
Сетка шире контейнера Проценты плюс фиксированные треки или длинное слово в 1fr Перейти на fr, minmax(0, 1fr)
grid-template-areas игнорируется Область не прямоугольник или разное число ячеек Выровнять карту, пустые ячейки — точкой
justify-content ничего не делает Треки fr уже заняли всю ширину Задать треки фиксированной ширины или двигать элементы через *-items
Лишняя строка внизу Элементов больше ячеек, созданы неявные треки Добавить ряды или задать grid-auto-rows

В DevTools Chrome и Firefox рядом с контейнером есть бейдж grid: он включает наложение с номерами линий и именами областей.

Выводы

  • Grid раскладывает прямых потомков контейнера сразу по строкам и столбцам; для потока в одну линию удобнее Flexbox.
  • fr делит место, оставшееся после фиксированных треков и gap; проценты с фиксированными колонками легко переполняют контейнер.
  • repeat(auto-fill или auto-fit, minmax(...)) дает адаптивную сетку без медиазапросов, а minmax(0, 1fr) защищает от распирания длинным содержимым.
  • Области в grid-template-areas обязаны быть прямоугольными, иначе объявление отбрасывается целиком.
  • subgrid выравнивает содержимое соседних карточек по общим трекам родителя и работает во всех актуальных браузерах.

Где применяется / связь с практикой

Освойте тему на практике

Grid — основной инструмент для каркаса страниц, каталогов, дашбордов и лендингов. Системно пройти HTML, CSS, адаптивность и сборку макета в живой проект можно на курсе HTML/CSS. Отдельные темы верстки удобно разобрать с преподавателями на бесплатных открытых уроках Otus.

FAQ

Можно ли верстать таблицы данных на Grid вместо <table>?
Для табличных данных лучше оставить <table>: у нее есть семантика строк и заголовков для скринридеров. Grid подходит для раскладки, а не для смысла данных.

Нужен ли префикс -ms- для Grid?
Нет: старый синтаксис -ms-grid был нужен только Internet Explorer, поддержка которого Microsoft прекращена в 2022 году.

Как сделать масонри-раскладку (кладку разной высоты) на Grid?
Штатного стабильного решения пока нет: спецификация masonry в CSS еще в работе, поэтому используют колонки CSS (columns) или JavaScript.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)