Интринсик-вёрстка, aspect-ratio, subgrid

Доверяем размеры самому контенту: интринсик-ключевые слова, пропорции через aspect-ratio, выравнивание вложенных сеток subgrid и знакомство с container queries.

Интринсик-вёрстка — это подход, где размеры элементов определяет их собственное содержимое (через min-content, max-content, fit-content()), а не жёстко заданные пиксели.

Зачем это знать на практике

Жёсткие width: 300px ломаются, как только меняется контент или язык интерфейса: немецкое слово длиннее английского, цена бывает в три цифры, бывает в семь. Интринсик-подход перекладывает решение о размере на сам контент — вёрстка перестаёт «трещать». Сюда же — aspect-ratio, который держит пропорции картинок и видео без хаков с padding; subgrid, выравнивающий поля вложенных карточек по общей сетке; и container queries, реагирующие на размер родителя, а не всего окна. Это инструменты современной устойчивой вёрстки.

Интринсик-размеры: min-content, max-content, fit-content

Эти ключевые слова описывают размер «по содержимому» и работают как значения width, а также внутри minmax() и шаблонов Grid:

ЗначениеСмысл
min-contentсамый узкий размер без переполнения — ширина самого длинного неразрывного слова
max-contentразмер, при котором контент не переносится вовсе — текст в одну строку
fit-content(L)как max-content, но не больше лимита L; при нехватке места переносится
/* колонка ровно под самое длинное слово заголовков */
.tags { width: min-content; }

/* подпись не шире 30ch, но если контент короче — сожмётся по нему */
.caption { width: fit-content(30ch); }

В Grid это особенно полезно: grid-template-columns: max-content 1fr делает первую колонку ровно под её содержимое (например, под подписи формы), а вторую — резиновой. Никаких магических пикселей.

aspect-ratio: пропорции без хаков

Раньше квадратную или 16:9 область делали через «padding-hack» (padding-top: 56.25%). Свойство aspect-ratio задаёт пропорцию напрямую: укажите соотношение, а высота посчитается из ширины (или наоборот).

.thumb {
  width: 100%;
  aspect-ratio: 16 / 9;   /* высота = ширина × 9/16 */
  object-fit: cover;       /* для img/video — заполнить, обрезав лишнее */
}

.avatar {
  width: 64px;
  aspect-ratio: 1;        /* идеальный квадрат */
  border-radius: 50%;
}

Если задать и height, и aspect-ratio, то явная высота побеждает — соотношение действует, только пока одна из сторон не зафиксирована. Огромный практический плюс: зарезервированное соотношение убирает «прыжки» вёрстки (layout shift), пока грузится картинка, — место под неё уже занято.

subgrid: вложенная сетка наследует линии родителя

Классическая боль: ряд карточек, в каждой заголовок, текст и кнопка. Тексты разной длины — и кнопки оказываются на разной высоте, ряд выглядит неряшливо. Раньше выравнивали фиксированными высотами. subgrid решает это честно: вложенный grid берёт те же линии, что и родитель.

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  grid-auto-rows: subgrid;     /* строки задаёт родитель */
  gap: 16px;
}
.card {
  display: grid;
  /* три ряда карточки выравниваются по соседям */
  grid-template-rows: subgrid;
  grid-row: span 3;            /* карточка занимает 3 строки родителя */
}

Слово subgrid в grid-template-rows (или -columns) означает: «не создавай свои треки, используй треки и линии родительской сетки». В результате заголовки всех карточек на одной высоте, тексты — на одной, кнопки — на одной, хотя контента в каждой карточке разное количество. До subgrid такого выравнивания «насквозь» нельзя было добиться без скриптов.

container queries против media queries: введение

Медиазапросы реагируют на размер окна. Но компонент часто должен зависеть от своего контейнера: одна и та же карточка в широком сайдбаре и в узкой колонке должна выглядеть по-разному, хотя окно одно. Это и есть container queries.

/* объявляем контейнер «опросным» */
.card-host {
  container-type: inline-size;
  container-name: card;
}

/* стили зависят от ширины КОНТЕЙНЕРА, а не окна */
@container card (min-width: 360px) {
  .card { grid-template-columns: 120px 1fr; } /* картинка сбоку */
}
Аспектmedia queriescontainer queries
На что смотрятразмер окна / устройстваразмер ближайшего опросного контейнера
Где уместныглобальный макет страницыпереиспользуемые компоненты
Единицыpx, vw и т.п.+ контейнерные cqw, cqh, cqi

Правило выбора: верстаете страницу целиком —media queries; делаете компонент, который встанет в разные места, — container queries. Они не конкуренты, а дополняют друг друга.

Как это работает под капотом

Интринсик-размеры — это результат того же «прохода по содержимому», что браузер и так делает для раскладки. min-content — это ширина при максимальном переносе (узкий столбик из слов), max-content — при полном отсутствии переноса (одна длинная строка); fit-content(L) просто зажимает max-content сверху лимитом L. aspect-ratio добавляет в алгоритм размеров одно правило: если задана одна сторона и не задана другая, вторая вычисляется из соотношения; поэтому свойство «уступает» явным width/height. subgrid устроен принципиально иначе, чем обычная вложенная сетка: дочерний контейнер не заводит собственные треки, а получает ссылку на линии и размеры родителя в том диапазоне строк/столбцов, который он занимает (grid-row: span N). Поэтому выравнивание «насквозь» получается само: все карточки делят буквально одни и те же линии. А container queries требуют, чтобы у элемента-предка был объявлен container-type — это превращает его в «точку отсчёта размеров», относительно которой и срабатывают @container-правила и единицы cq*.

Частые ошибки

Ждут, что aspect-ratio сработает при заданных обеих сторонах. Если есть и width, и height, соотношение игнорируется — оно вычисляет лишь недостающую сторону. Оставьте одну сторону auto.

Забывают container-type у контейнера. @container-правила молча не срабатывают, если у предка не объявлен container-type (обычно inline-size). Само правило при этом не выдаёт ошибку.

Путают, чей размер опрашивается. @container смотрит на контейнер, а не на сам стилизуемый элемент; контейнером становится ближайший предок с container-type, а не элемент, к которому применяется правило.

Subgrid без span на ребёнке. Чтобы карточка делила строки родителя, она должна занять их явно — grid-row: span 3. Без указания диапазона grid-template-rows: subgrid нечего наследовать.

Итоги

  • min-content/max-content/fit-content() задают размер по содержимому — вёрстка не ломается от длины контента.
  • aspect-ratio: 16 / 9 держит пропорции без padding-хаков и убирает layout shift; при двух заданных сторонах не действует.
  • subgrid заставляет вложенную сетку использовать линии родителя — поля и кнопки карточек выравниваются «насквозь».
  • container queries реагируют на размер контейнера (нужен container-type), media queries — на размер окна; они дополняют друг друга.
  • Под капотом интринсик-размеры — это проходы «min/max-content», а subgrid делит линии родителя вместо собственных треков.
Проверьте себя
1. Чем aspect-ratio: 16 / 9 лучше старого padding-hack для пропорциональных блоков?
AЗадаёт пропорцию напрямую и резервирует место заранее, убирая layout shift при загрузке картинки
BРаботает только в Grid
CДелает элемент всегда квадратным независимо от значения
DПолностью заменяет object-fit
2. Что делает grid-template-rows: subgrid на дочерней карточке?
AЗаставляет карточку использовать строки и линии родительской сетки, а не создавать свои
BСоздаёт у карточки независимую сетку с собственными треками
CОтключает Grid внутри карточки
DПревращает строки в колонки
3. Чем container queries отличаются от media queries?
Acontainer queries реагируют на размер контейнера-предка (нужен container-type), а media queries — на размер окна
Bcontainer queries реагируют на размер окна, а media queries — на размер элемента
CЭто одно и то же с разным синтаксисом
Dcontainer queries работают только для печати