← все статьи
7 мин

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.
← все статьи Следующая →FastAPI для продакшна: чеклист