Subgrid и сложные сетки
Почему кнопки в карточках стоят на разной высоте и как заставить вложенные сетки подчиняться общим строкам.
Что ты научишься делать
- Поймёшь, почему вложенная сетка не выравнивается с соседями
- Включишь subgrid по строкам и получишь ровные заголовки и кнопки
- Разложишь большой макет на именованные области и линии
#Знакомая неровность
Три карточки в ряд. У первой заголовок в одну строку, у второй — в три. Кнопки внизу оказываются на разной высоте, и весь ряд выглядит неопрятно.
Причина не в ошибке. Каждая карточка — своя сетка, со своими строками. Родительский грид знает только про колонки, а что происходит внутри карточек, ему безразлично.
subgrid отменяет эту изоляцию: карточка перестаёт заводить свои строки и встраивается в строки родителя.
#Как это пишется
Нужны две вещи. Родитель объявляет строки, а ребёнок занимает их все сразу и говорит, что берёт строки родителя.
.cards { display: grid; grid-template-columns: repeat(3, 1fr); /* Четыре строки: заголовок, текст, цена, кнопка */ grid-template-rows: auto 1fr auto auto; gap: 16px;}.card { display: grid; /* Карточка растягивается на все четыре строки родителя… */ grid-row: span 4; /* …и берёт их вместо своих */ grid-template-rows: subgrid;}#Subgrid по колонкам
Тот же приём работает и по горизонтали. Классический случай — форма, где подписи и поля должны стоять в две ровные колонки, а каждая пара при этом остаётся отдельным блоком со своей рамкой.
.form { display: grid; /* Колонка подписей ровно по самой длинной из них */ grid-template-columns: auto 1fr; gap: 12px;}.form__row { display: grid; grid-column: span 2; grid-template-columns: subgrid; align-items: center;}#Именованные линии и области
В большом макете считать номера линий невозможно: добавил колонку — и все числа поехали. Линиям и областям дают имена.
.page { display: grid; grid-template-columns: [full-start] minmax(16px, 1fr) [content-start] minmax(0, 1100px) [content-end] minmax(16px, 1fr) [full-end];}/* Обычный блок живёт в колонке контента */.page > * { grid-column: content;}/* А этот разворачивается во всю ширину экрана */.hero { grid-column: full;}Приём называют «полноширинный прорыв»: сетка страницы задаётся один раз, и любой блок может выйти на всю ширину одной строкой, без отрицательных отступов и обёрток.
Пара имён content-start и content-end автоматически даёт область content — писать grid-column: content достаточно.
#minmax, auto-fit и auto-fill
Две функции, без которых большие сетки не собираются. minmax() задаёт вилку: «не уже такого, но тянись до такого». repeat() с ключевыми словами позволяет не указывать количество колонок вообще.
Разница между auto-fit и auto-fill сбивает с толку всех, а видна только когда элементов мало.
| Запись | Поведение при трёх карточках в широком ряду |
|---|---|
repeat(auto-fill, minmax(240px, 1fr)) | Создаёт колонки под всю ширину, лишние остаются пустыми. Карточки не растягиваются |
repeat(auto-fit, minmax(240px, 1fr)) | Пустые колонки схлопываются, три карточки растягиваются на всю ширину |
Правило выбора простое: если хочешь, чтобы неполный ряд растянулся, — auto-fit. Если карточки должны сохранять ширину и прижиматься к началу, — auto-fill.
#Как это выглядит в реальном проекте
@layer components { .catalog { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); /* Строки перечислены явно — иначе subgrid не к чему привязать */ grid-auto-rows: auto 1fr auto auto; gap: 20px; } .product { display: grid; grid-row: span 4; grid-template-rows: subgrid; gap: 8px; } /* Длинные названия больше не ломают ряд: кнопки и цены стоят на одной линии во всех карточках */ .product__title { margin: 0; font-size: 1rem; } .product__price { align-self: end; font-weight: 700; }}#Отладка сеток
Сетку почти невозможно отлаживать по коду — её нужно видеть. В инструментах разработчика рядом с элементом, у которого стоит display: grid, появляется значок grid. Включи его.
Браузер нарисует поверх страницы линии сетки с номерами и именами. Сразу видно, где линия на самом деле проходит, почему блок занял не ту ячейку и куда делось пустое место. У вложенного subgrid линии продолжают родительские — это лучший способ убедиться, что подчинение действительно включилось.
#Про masonry
Раскладка «кирпичной кладкой», где карточки разной высоты подтягиваются вверх без просветов, долго существовала только на скриптах.
В CSS она обсуждается и уже есть в некоторых браузерах за флагом или в экспериментальном виде, но единого решения пока нет: спецификация менялась несколько раз. На неё сегодня не закладываются — если такая раскладка нужна в работе, берут колоночный вариант через columns или скрипт.
#Когда НЕ надо
- Когда хватает
align-items: stretch. Если внутри карточки один-два элемента, ровные кнопки получаются обычным флексом сmargin-top: auto. Subgrid — для случая, когда линий несколько. - Для двухуровневой вложенности «на всякий случай». Subgrid связывает ребёнка с родителем жёстко: поменяешь строки у родителя — придётся править и
grid-row: spanу всех детей. - Ради выравнивания текста внутри одной карточки. Это задача обычной сетки, лишний уровень тут ничего не даст.
#Поддержка браузерами
| Возможность | Состояние |
|---|---|
subgrid | Firefox первым, с 2019; Safari и Chrome — к концу 2023. Сегодня во всех современных браузерах |
| Именованные линии и области | Поддерживаются везде с самого появления grid |
masonry | Единого решения нет, спецификация в работе. В продакшене не использовать |
/* База: работает всюду, кнопки прижаты вниз */.card { display: flex; flex-direction: column; }.card__btn { margin-top: auto; }/* Улучшение: строки выравниваются между карточками */@supports (grid-template-rows: subgrid) { .card { display: grid; grid-row: span 4; grid-template-rows: subgrid; }}Где спотыкаются почти все
Написан subgrid без grid-row: span
Почему Ребёнок занимает одну строку родителя, подчиняться нечему. Внешне ничего не меняется, и кажется, что subgrid «не работает».
Как надо Сколько строк перечислено у родителя — столько занимает ребёнок: grid-row: span 4.
У родителя строки не заданы явно
Почему Если строк нет в grid-template-rows или grid-auto-rows, subgrid не к чему привязаться.
Как надо Перечисли строки у родителя — по одной на каждый ряд содержимого карточки.
gap задан и у родителя, и у ребёнка
Почему При subgrid ребёнок наследует расстояния родителя по этой оси. Свой gap перекроет их, и выравнивание поедет.
Как надо Задавай gap по подчинённой оси только у родителя.
Ставка на masonry в рабочем проекте
Почему Спецификация ещё меняется, поведение в браузерах разное. Код придётся переписывать.
Как надо Пока бери columns или скрипт, а masonry держи на заметке.
- Subgrid subgrid
- Значение
grid-template-rowsиgrid-template-columns: вложенная сетка берёт линии родителя вместо своих. - Именованная линия named grid line
- Имя в квадратных скобках в
grid-template-columns. Позволяет не считать номера. - Полноширинный прорыв full-bleed
- Приём: блок выходит за колонку контента на всю ширину экрана средствами сетки.
- Masonry masonry
- Раскладка кирпичной кладкой без просветов. В CSS пока не стандартизована.
Проверь себя
Родительский грид управляет только карточками целиком. Что внутри — забота каждой карточки отдельно.
Ребёнок должен занять столько строк родителя, сколько собирается подчинить. Иначе подчинять нечего.
Пара имён с суффиксами <code>-start</code> и <code>-end</code> автоматически образует именованную область.
Пока берут <code>columns</code> или скрипт. С masonry код пришлось бы переписывать вслед за спецификацией.
Одну линию выравнивания даёт обычный флекс. Subgrid нужен, когда линий несколько и они должны совпасть у соседей.
Задание
Выровнять ряд карточек
Три карточки с текстом разной длины. Цены и кнопки разъехались по высоте.
Задай родителю четыре строки и включи subgrid у карточек так, чтобы заголовки, тексты, цены и кнопки встали в четыре ровные линии. Разметку не трогай.
Подсказка 1
Родителю нужны четыре строки: grid-template-rows: auto 1fr auto auto;
Подсказка 2
Карточке — две строки кода: grid-row: span 4; и grid-template-rows: subgrid;
Показать решение
<div class="cards"> <article class="card"> <h3>Эспрессо</h3> <p>Двойная порция.</p> <p class="price">120 ₽</p> <button type="button">Заказать</button> </article> <article class="card"> <h3>Раф на кокосовом молоке</h3> <p>Мягкий сладковатый напиток для тех, кому обычный латте кажется слишком резким.</p> <p class="price">240 ₽</p> <button type="button">Заказать</button> </article> <article class="card"> <h3>Фильтр</h3> <p>Зерно из Эфиопии.</p> <p class="price">180 ₽</p> <button type="button">Заказать</button> </article></div>.cards { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: auto 1fr auto auto; gap: 12px;}.card { display: grid; grid-row: span 4; grid-template-rows: subgrid; gap: 6px; padding: 12px; background: #f5f5fa; border-radius: 10px;}.card h3 { margin: 0; font-size: 15px; }.card p { margin: 0; font-size: 13px; color: #5b6070; }.price { font-weight: 700; }button { padding: 8px; font: inherit; font-size: 13px; color: #fff; background: #6544e0; border: 0; border-radius: 8px;}