Container queries в CSS
Nesting держит селекторы компонента вместе. Media queries по-прежнему смотрят на viewport. Container queries задают другой вопрос: какова ширина этого родительского блока? Одна карточка товара остаётся компактной в узком rail и раскрывается в широкой колонке — даже если окно браузера не менялось.
Почему viewport недостаточно
В вёрстке один и тот же компонент часто попадает в слоты разной ширины: боковой rail 16rem и гибкая основная область. У обоих одна ширина viewport, поэтому @media (min-width: …) срабатывает одинаково. Нужны стили от контейнера вокруг карточки, а не от всего экрана.
Создать контекст измерения
Выберите родителя, который должен сообщать размер. Задайте container-type — обычно inline-size, чтобы запрашивать inline-размер родителя (ширину при горизонтальном письме) без полного size containment на обеих осях:
.rail {
container-type: inline-size;
container-name: rail;
width: 16rem;
padding: 1rem;
background: #f8fafc;
}
.stage {
container-type: inline-size;
container-name: stage;
flex: 1;
min-width: 0;
padding: 1.25rem;
}
Без container-type у @container нечего надёжно измерять. container-name необязателен, но полезен, когда несколько предков — контейнеры: можно прицелиться по имени.
Запрос к контейнеру через @container
После настройки оберните правила в @container с условием по размеру. Потомки реагируют, когда этот блок подходит — а не только когда меняется viewport:
.product-card {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
background: #fff;
}
.product-card .media {
aspect-ratio: 16 / 10;
border-radius: 0.5rem;
background: #cbd5e1;
}
.product-card .meta {
font-size: 0.875rem;
color: #64748b;
}
@container (min-width: 28rem) {
.product-card {
grid-template-columns: 10rem 1fr;
align-items: center;
}
.product-card .title {
font-size: 1.25rem;
}
}
В узком .rail карточка остаётся в одной колонке. Тот же HTML в широком .stage включает две колонки — тот же разметка, тот же viewport, другая ширина контейнера.
Именованные контейнеры
Если вложенные контейнеры путают, какой блок измеряется, укажите имя после @container:
@container stage (min-width: 36rem) {
.product-card {
grid-template-columns: 12rem 1fr auto;
gap: 1.25rem;
}
.product-card .buy {
justify-self: end;
}
}
@container rail (max-width: 18rem) {
.product-card .meta {
display: none;
}
}
@container stage (…) смотрит только на предка с именем stage. Жёсткие правила rail не спорят с просторными правилами stage, когда оба оборачивают связанную разметку.
Единицы container query
При наличии контекста containment длины вроде cqi (1 % inline-размера query-контейнера) и cqw (1 % его ширины) масштабируют тип и отступы с родителем, а не с viewport (vi / vw):
.product-card .title {
font-size: clamp(1rem, 0.85rem + 2.5cqi, 1.5rem);
}
.product-card {
padding: clamp(0.75rem, 2.5cqi, 1.5rem);
}
Сочетайте с var() и clamp() из прошлых уроков Modern Toolkit. Container-единицы измеряют контекст для запроса — удобно для переиспользуемых виджетов.
Контейнер и media — кто за что отвечает
📊 Container queries vs media queries
| Какой вопрос вы задаёте | Предпочтительно | Типичный инструмент |
|---|---|---|
| Насколько широк viewport браузера? | Оболочка страницы, nav, многоколоночная страница | @media (min-width: …) |
| Насколько широк этот родитель вокруг компонента? | Карточки, виджеты, embed в неравных слотах | @container (min-width: …) после container-type |
| Нужны оба? | Часто да | MQ для страницы; CQ для компонента в слоте |
Два слота, один компонент
Небольшой эскиз показывает выгоду — два контейнера, один класс карточки:
.page {
display: flex;
gap: 1rem;
align-items: start;
}
.rail {
container-type: inline-size;
container-name: rail;
width: 16rem;
}
.stage {
container-type: inline-size;
container-name: stage;
flex: 1;
min-width: 0;
}
/* Same .product-card class in both slots — @container rules above decide layout */
Не нужно плодить .product-card--sidebar и .product-card--main только из-за ширины слота. Слот сообщает размер; карточка слушает.
Сочетать media страницы и контейнеры компонента
Viewport-запросы оставьте для решений страницы (свернуть nav, одна колонка оболочки). Container queries — для компонента в любом слоте, который дала страница:
@media (max-width: 40rem) {
.page {
flex-direction: column;
}
.rail {
width: auto;
}
}
@container (min-width: 28rem) {
.product-card {
grid-template-columns: 10rem 1fr;
}
}
На узком телефоне страница складывается в колонку; на широком desktop rail может оставаться узким, а stage широким — это различие чётко выражают только container queries.
Попробуйте сами
- Добавьте
container-type: inline-sizeна обёртку карточки; в DevTools проверьте контекст containment. - Напишите
@container (min-width: 24rem)— из стека в две колонки. - Задайте
container-name: panelи прицельтесь@container panel (min-width: 30rem). - Заголовок через
cqi; меняйте только ширину родителя (не окна). - Оставьте один
@mediaна оболочке страницы; не дублируйте ту же логику в карточке.
Частые ошибки
@containerбезcontainer-typeу предка — нечего измерять- Заменить все media queries на container queries — оболочка страницы всё ещё для
@media - Три вложенных контейнера и неясно, какая ширина «побеждает» — назовите и цельтесь явно
- Слепо копировать breakpoints viewport (
48rem) в каждый виджет — настраивайте под слот, не каталог устройств - Путать nesting (
&, вложенные селекторы) и containment — nesting упорядочивает селекторы; container queries измеряют блоки
Итог
- Container queries стилизуют от размера родителя, не только viewport
container-type(частоinline-size); при необходимостиcontainer-name@container (min-width: …)/@container name (…)для перестройки потомковcqi/cqw, когда длины должны следовать за контейнером- Дальше в Modern Toolkit: feature queries (
@supports) — стили от возможностей браузера
🧠 Проверьте Свои Знания
Проверьте Свои Знания
Бросьте себе вызов с помощью этой интерактивной викторины и узнайте, насколько хорошо вы понимаете тему
📝 Инструкции
- Внимательно прочитайте каждый вопрос
- Выберите лучший ответ на каждый вопрос
- Вы можете пересдавать тест столько раз, сколько захотите
- Ваш прогресс будет показан вверху