Медиазапросы удобны для перестройки всей страницы по размеру окна, но переиспользуемый компонент часто не знает, где окажется: в широкой ленте, узком сайдбаре или сетке. CSS container queries решают эту задачу на уровне компонента: условия в `@container` проверяют характеристики подходящего контейнера-предка, а не ширину viewport, поэтому один и тот же блок может вести себя по-разному в разных местах страницы.
Такой подход особенно полезен для карточек и виджетов, которые дизайнеры переиспользуют в нескольких шаблонах. Компонент получает один базовый CSS, но реагирует на реальную ширину своей области, поэтому его не приходится дублировать под каждую страницу.
Объявляем контейнер
Для запроса по ширине родителю компонента задают контекст размерного контейнера. Значение `inline-size` позволяет проверять размер по строчной оси, которая в обычном горизонтальном письме соответствует ширине; это удобно для карточек, виджетов и блоков, которые переезжают между основной колонкой, сеткой и боковой областью. Важно объявлять контейнер на предке компонента, а не на самой карточке, чтобы правило ниже проверяло именно доступное внешнее пространство.
В разметке таким предком часто становится обёртка списка, ячейка сетки или зона сайдбара. Если компонент потом перенесут в другое место, достаточно оставить тот же класс контейнера, а логика перестройки сохранится.
«`css .card-host { container-type: inline-size; } «`
Само объявление не меняет карточку и не должно быть единственным условием читаемости. Оно сообщает браузеру, что потомки могут использовать этот элемент как контейнер для размерного запроса, а затем уже правило `@container` добавляет расширенную компоновку для тех мест, где у компонента действительно хватает пространства.
На практике это удобно проверять прямо в DevTools, меняя ширину родительской области или перетаскивая компонент между колонками. Если карточка без `@container` уже выглядит аккуратно, запрос становится улучшением, а не единственным способом спасти макет.
Перестраиваем карточку через @container
Сначала задайте компактную компоновку по умолчанию, а затем добавьте вариант для достаточно широкого контейнера. Такой порядок делает компонент устойчивым: базовый вариант остаётся рабочим без запроса, а улучшение включается только в подходящем контейнере.
Компактный вариант лучше считать основным состоянием компонента, потому что он выдерживает узкие колонки, виджеты и fallback-сценарии. Широкий вариант отвечает только за дополнительную композицию: изображение и текст получают больше места, но смысл карточки остаётся тем же.
«`css .article-card { display: grid; gap: 1rem; }
.article-card__image { inline-size: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
@container (width >= 36rem) { .article-card { grid-template-columns: minmax(12rem, 2fr) 3fr; align-items: start; } } «`
Одна и та же карточка останется одноколоночной в узкой области и станет двухколоночной там, где её собственный контейнер шире `36rem`. Это отличает запрос от `@media`: перенос компонента в другую часть страницы не требует привязывать его поведение к общей ширине окна.
Если такая карточка стоит в основной ленте на десктопе, она может раскрыться в две колонки. Та же карточка в боковой колонке останется компактной, хотя viewport у пользователя не изменился.
Именуем контейнер, когда уровней несколько
Без имени запрос ищет ближайший подходящий контейнер-предок, и это поведение удобно для простых компонентов. В сложной вложенной разметке намерение лучше зафиксировать явно, чтобы карточка реагировала на нужную область интерфейса, а не на случайный ближайший контейнер.
Именованный контейнер делает зависимость читаемой для следующего разработчика. По одному правилу видно, что карточка должна слушать именно поток новостей `stream`, а не внутреннюю декоративную обёртку.
«`css .news-stream { container: stream / inline-size; }
@container stream (width >= 48rem) { .article-card__title { font-size: 1.75rem; } } «`
Сокращённое свойство `container` в этом примере одновременно задаёт имя `stream` и тип `inline-size`. Имя чувствительно к регистру и ограничивает выбор контейнера в правиле `@container`, поэтому такой приём особенно полезен в лентах, где карточка может оказаться внутри нескольких обёрток с разной ролью. Если имя не совпадёт, браузер не будет молча угадывать другой named-контекст, так что такие названия стоит держать стабильными рядом с CSS компонента.
Имена контейнеров не заменяют архитектуру классов, но хорошо документируют связь между областью страницы и поведением компонента. Это снижает риск, что будущая обёртка случайно станет новым ближайшим контейнером и изменит размер заголовка.
Размеры относительно контейнера
Для плавных размеров можно использовать container query units. Единица `cqi` равна одному проценту строчного размера query-контейнера, поэтому типографика может меняться не по ширине окна, а по реальному месту, которое получил компонент. В примере ниже `clamp()` оставляет жёсткие нижнюю и верхнюю границы, а середина формулы реагирует на контейнер.
Такой расчёт полезен там, где резкий скачок размера заметен сильнее, чем плавная адаптация. При этом `clamp()` оставляет контроль у разработчика: текст не раздувается бесконечно и не становится слишком мелким в узкой карточке.
«`css @container stream (width >= 30rem) { .article-card__title { font-size: clamp(1.25rem, 1rem + 1.5cqi, 2rem); } } «`
Ограничения `clamp()` не дают тексту стать слишком мелким или чрезмерно крупным. Если подходящего query-контейнера для оси нет, спецификация определяет для container-relative units запасное вычисление через small viewport units, поэтому отсутствие контейнера не следует использовать как скрытую часть дизайна: контекст лучше объявлять явно.
Из этого следует простое правило проверки: сначала убедитесь, что нужный контейнер действительно существует, а затем смотрите на вычисленный размер. Если размер внезапно похож на viewport-зависимый, значит компонент, скорее всего, не нашёл ожидаемый query-контейнер.
Что проверить перед внедрением
Container queries не отменяют базовые стили: сначала компонент должен оставаться читаемым без условия, а запрос добавляет улучшенную компоновку. Проверяйте его не только на разных ширинах окна, но и в каждом реальном месте размещения, потому что одинаковая ширина viewport может давать карточке совершенно разное пространство внутри сетки, сайдбара или ленты.
Отдельно проверьте состояния с длинным заголовком, отсутствующим изображением и разной плотностью соседних карточек. Container query меняет компоновку, но не решает сам по себе переполнение текста, неудачные пропорции изображения или слишком агрессивные отступы.
Для размерного запроса нужен подходящий предок: сам элемент не может менять собственные стили на основании своей же ширины через размерный `@container`. Если контейнеры вложены, имя помогает избежать случайного выбора ближайшего контекста, а поддержку конкретных разновидностей запросов и синтаксиса нужно сверять с браузерами проекта. Этот материал использует базовые size container queries, `container-type`, именованные контейнеры и `cqi`, не выдавая более новые style или scroll-state queries за обязательную часть примера.
Если проект поддерживает несколько дизайн-систем или встраиваемых блоков, зафиксируйте в документации, где объявляется контейнер и какие компоненты его слушают. Тогда container queries останутся локальным инструментом компонента, а не скрытой зависимостью всей страницы.