CSS Container Queries: конец медиа-запросов?
Стандартный подход: написал карточку, добавил @media (max-width: 768px), подогнал стили. Работает — но только пока карточка стоит на фиксированной позиции в лейауте. Как только она попадает в сайдбар, в модалку, в grid с переменным числом колонок — медиа-запрос ломается. Он знает только ширину вьюпорта, а не реальное пространство, доступное компоненту.
Container Queries решают ровно эту задачу. Компонент реагирует на размер своего контейнера, а не всего экрана. Звучит как мелочь — на практике это меняет архитектуру CSS.
Почему @media недостаточно
Рассмотрим конкретный случай. Есть карточка товара. В основной сетке — три колонки, карточка широкая, показываем изображение слева, текст справа. В сайдбаре «похожие товары» — одна колонка, карточка узкая, нужна вертикальная раскладка.
С @media логика привязана к вьюпорту: на мобильном всё вертикально, на десктопе всё горизонтально. Но на десктопе сайдбар — узкий. Карточка в сайдбаре на десктопе получает горизонтальный лейаут в 200px — и выглядит сломанной.
Обходной путь — добавить модификатор: .card--sidebar, .card--compact. Это ручное управление тем, что должно быть автоматическим. Container Queries убирают этот бойлерплейт полностью.
@container и container-type: основы
Чтобы использовать Container Queries, нужно сначала объявить контейнер. Это делается через свойство container-type:
/* Объявляем контейнер */
.card-wrapper {
container-type: inline-size;
/* или: size — учитывает и ширину, и высоту */
/* или: normal — только стилевые запросы, без размерных */
}
/* Пишем запрос относительно контейнера */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
gap: 1rem;
}
}
@container (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}
Правило простое: container-type: inline-size — самый частый вариант. Он говорит браузеру отслеживать горизонтальный размер элемента. Дочерние элементы могут использовать @container для запросов к этому размеру.
Можно именовать контейнеры, чтобы не было путаницы в сложных вложенных структурах:
.sidebar {
container-type: inline-size;
container-name: sidebar;
/* Сокращённая запись: */
/* container: sidebar / inline-size; */
}
.main-grid {
container-type: inline-size;
container-name: main;
}
/* Запрос только к конкретному контейнеру */
@container sidebar (max-width: 280px) {
.card__image {
display: none;
}
}
@container main (min-width: 600px) {
.card__image {
width: 200px;
}
}
Именование критично, когда компонент может находиться в разных контейнерах и нужна разная логика для каждого.
Паттерн: адаптивный компонент
Самый полезный паттерн — компонент, который сам знает, как себя отображать, вне зависимости от того, где он стоит. Полностью самодостаточный.
/* Контейнер всегда объявляется на обёртке, а не на самом компоненте */
.product-card-wrapper {
container-type: inline-size;
container-name: product-card;
}
/* Базовый стиль — мобильный, вертикальный */
.product-card {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
}
.product-card__image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 8px;
}
.product-card__badge {
display: none;
}
/* Средний контейнер — горизонтальная раскладка */
@container product-card (min-width: 320px) {
.product-card {
flex-direction: row;
align-items: flex-start;
}
.product-card__image {
width: 120px;
flex-shrink: 0;
aspect-ratio: 1;
}
}
/* Широкий контейнер — полная версия с бейджем и описанием */
@container product-card (min-width: 500px) {
.product-card {
gap: 1.5rem;
padding: 1.5rem;
}
.product-card__image {
width: 200px;
}
.product-card__badge {
display: inline-block;
}
.product-card__description {
-webkit-line-clamp: unset;
display: block;
}
}
Эта карточка корректно отображается в сайдбаре шириной 250px, в основной сетке на 1200px и в мобильном лейауте — без единого @media и без модификаторов. Логика находится в самом компоненте.
Единицы измерения cqi, cqb, cqw, cqh
Container Queries добавляют новые единицы — аналог vw/vh, но относительно контейнера:
.card {
container-type: inline-size;
container-name: card;
}
.card__title {
/* 5% от inline-размера ближайшего контейнера */
font-size: clamp(1rem, 3cqi, 1.5rem);
}
.card__hero {
/* 80% от ширины контейнера */
width: 80cqw;
/* 50% от высоты контейнера (нужен container-type: size) */
height: 50cqh;
}
cqi — процент от inline-размера (чаще всего ширина), cqb — от block-размера (высота), cqw/cqh — от ширины/высоты контейнера явно. Это позволяет делать fluid typography не от вьюпорта, а от компонента.
Style Queries: запросы к CSS-свойствам
Менее известная часть спецификации — Style Queries. Они позволяют реагировать не на размер контейнера, а на значение его CSS custom property:
.theme-wrapper {
container-type: normal; /* size не нужен для style queries */
--variant: compact;
}
/* Реагируем на значение custom property контейнера */
@container style(--variant: compact) {
.card {
padding: 0.5rem;
font-size: 0.875rem;
}
}
@container style(--variant: featured) {
.card {
border: 2px solid var(--accent);
padding: 2rem;
font-size: 1.125rem;
}
}
@container style(--variant: minimal) {
.card__image,
.card__badge {
display: none;
}
}
Практический сценарий: дизайн-система с вариантами компонентов. Вместо того чтобы передавать модификаторы через HTML-атрибуты или JS, достаточно установить CSS custom property на контейнере — и все дочерние компоненты адаптируются автоматически. Это чистая CSS-инкапсуляция без JavaScript.
На январь 2026 года Style Queries поддерживаются в Chrome/Edge с версии 111 и Firefox с 117. Safari поддерживает с версии 17.2. Для production рекомендуется проверять поддержку через @supports:
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
}
/* Fallback без container queries */
.card {
display: flex;
flex-direction: column;
}
@supports (container-type: inline-size) {
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}
}
@media vs @container: когда что использовать
Оба инструмента остаются нужными — они решают разные задачи:
@media — для глобальных лейаутных решений, которые действительно зависят от вьюпорта: переключение одноколоночного/многоколоночного layout страницы, скрытие/показ навигации, изменение размеров шрифтов на уровне всего сайта.
@container — для компонентов, которые размещаются в разных контекстах: карточки, виджеты, блоки с данными, элементы дизайн-системы.
Хорошая эвристика: если стиль зависит от того, где стоит компонент, — используй @container. Если стиль зависит от устройства или вьюпорта — используй @media.
На практике они комбинируются. Макрострукtura страницы управляется через @media, а поведение каждого компонента внутри этой структуры — через @container:
/* Глобальный лейаут — @media */
@media (min-width: 1024px) {
.page-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 2rem;
}
}
/* Компонент внутри лейаута — @container */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main-content {
container-type: inline-size;
container-name: content;
}
@container sidebar (max-width: 280px) {
.widget {
font-size: 0.875rem;
}
}
@container content (min-width: 600px) {
.article-card {
grid-template-columns: 200px 1fr;
}
}
Поддержка браузеров и Baseline 2023
Container Queries (размерные) получили статус Baseline 2023: Chrome 105+, Firefox 110+, Safari 16+. Это означает широкую поддержку — более 93% пользователей по данным Can I Use. Для большинства проектов полифилл не нужен.
Style Queries находятся в Baseline Limited Availability — поддерживаются во всех major браузерах, но не во всех их версиях в дикой природе. Используйте с @supports или в проектах с контролируемой аудиторией.
Единицы cqi/cqb/cqw/cqh — Baseline 2023, поддержка совпадает с размерными запросами.
Container Queries не убивают медиа-запросы — они занимают свою нишу. @media управляет страницей, @container управляет компонентом. Компонент, написанный через @container, переносится между лейаутами без единой правки CSS.