
Почти двадцать лет адаптивный веб-дизайн жил по одному правилу: посмотри на размер экрана — подстройся под него. Media queries были единственным инструментом, и мы научились выживать с ним в мире компонентных UI-фреймворков. Но в 2023 году всё изменилось — container queries получили кроссбраузерную поддержку, и к 2026 году стали стандартом де-факто для компонентной адаптивности.
Эта статья — подробный разбор двух подходов: как они работают, чем отличаются, когда какой применять и какие реальные кейсы решают контейнерные запросы лучше, чем классические медиазапросы.
Media Queries: Фундамент, который не исчезнет
Медиазапросы появились в спецификации CSS ещё в 2012 году и совершили революцию. Принцип прост: браузер сообщает CSS характеристики устройства — ширину окна, ориентацию экрана, prefers-reduced-motion, цветовую схему — и вы пишете условия, при которых применяются те или иные стили.
/* Классический mobile-first подход */
.container {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.container {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 1024px) {
.container {
grid-template-columns: 1fr 1fr 1fr;
}
}
Что медиазапросы делают хорошо
- Макроструктура страницы. Переход между мобильной и десктопной раскладкой — сайдбар, хедер, основная сетка.
- Пользовательские предпочтения.
prefers-color-scheme,prefers-reduced-motion,prefers-contrast— это не размеры, а характеристики среды. - Характеристики устройства. Тип экрана, плотность пикселей, hover-возможности.
- Динамические единицы viewport.
dvh,svh,lvhрешают проблему мобильных браузеров с плавающей высотой адресной строки.
Где медиазапросы ломаются
Проблема фундаментальна: медиазапрос знает только о viewport, но не о контексте компонента.
Представьте карточку товара. Она может лежать:
- в широком основном контенте (800px)
- в узком сайдбаре (350px)
- в ячейке сетки из трёх колонок (400px)
На одном и том же экране, при одном и том же viewport. Медиазапрос вернёт одно и то же правило для всех трёх карточек, потому что viewport не изменился. Раньше разработчики решали это модификаторами классов: .card--horizontal, .card--compact, .card--sidebar — и каждый вариант требовал отдельного CSS и ручного управления в HTML.
Это не просто неудобство. Это архитектурный долг: компонент перестаёт быть независимым, потому что его внешний вид определяет место в DOM-дереве, а не его собственная логика.
Container Queries: Компонент, который знает о себе
Container queries решают ровно ту проблему, которую не могли решить медиазапросы. Вместо вопроса «какой размер у окна браузера?» компонент спрашивает «сколько места мне дал родитель?».
Как это работает: три шага
Шаг 1. Объявляем контекст контейнера на родительском элементе:
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Или shorthand */
.card-wrapper {
container: card / inline-size;
}
container-type: inline-size — самый частый выбор. Он отслеживает только ширину (inline-размер) и дешевле всего для браузера. size отслеживает и ширину, и высоту, но заставляет браузер блокировать layout на обеих осях, что может вызвать проблемы с производительностью.
Шаг 2. Пишем @container-запросы для дочерних элементов:
.card {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
}
/* Когда контейнер шире 400px — горизонтальная раскладка */
@container card (min-width: 400px) {
.card {
flex-direction: row;
align-items: center;
}
}
/* Когда контейнер шире 600px — увеличиваем типографику */
@container card (min-width: 600px) {
.card {
gap: 1.5rem;
}
.card__title {
font-size: 1.5rem;
}
}
Шаг 3. Вставляем компонент куда угодно. Один и тот же .card в сайдбаре (350px) останется вертикальным, в основном контенте (800px) станет горизонтальным с увеличенным шрифтом — без единого класса-модификатора.
Синтаксис range queries
С 2023 года поддерживается range-синтаксис, знакомый по медиазапросам:
@container (width >= 300px) {
.component { padding: 24px; }
}
@container (300px <= width <= 600px) {
.component { padding: 16px; }
}
Container Query Units: Адаптивность без брейкпоинтов
Контейнерные запросы принесли с собой новую систему единиц измерения — аналоги viewport-единиц, но привязанные к размеру контейнера.
| Единица | Относительно | Viewport-аналог | Когда использовать |
|---|---|---|---|
cqw | 1% ширины контейнера | vw | Горизонтальные отступы, ширина |
cqh | 1% высоты контейнера | vh | Sticky-элементы, вертикальные пропорции |
cqi | 1% inline-размера | vi | Типографика, отступы (рекомендуется) |
cqb | 1% block-размера | vb | Вертикальный ритм, стеки |
cqmin | Меньшее из cqi/cqb | vmin | Квадратные иконки, адаптивные блоки |
cqmax | Большее из cqi/cqb | vmax | Оверлеи на весь контейнер |
Практический пример — типографика, которая масштабируется вместе с контейнером, а не с экраном:
.card-title {
font-size: clamp(1rem, 4cqi, 2.5rem);
padding: 1cqi 2cqi;
}
.card-image {
width: 100%;
height: 30cqh;
object-fit: cover;
}
С cqi шрифт заголовка будет мельче в узком сайдбаре и крупнее в широком контенте — автоматически, без единого медиазапроса. Рекомендуется использовать cqi вместо cqw: cqi учитывает writing mode и корректно работает как в горизонтальной, так и в вертикальной системах письма.
Если в цепочке предков нет элемента с объявленным container-type, контейнерные единицы откатываются к соответствующим small viewport единицам (sv*).
Сравнение: Когда что использовать
Container queries не заменяют media queries — они их дополняют. Разделение зон ответственности:
| Параметр | Media Queries | Container Queries |
|---|---|---|
| Источник сигнала | Viewport браузера | Родительский элемент |
| Уровень адаптации | Страница, макросхема | Компонент, микросхема |
| Модульность | Низкая — глобальные правила | Высокая — компонент самостоятелен |
| Реиспользование | Требует модификаторов | Работает из коробки |
| Пользовательские prefs | Да (prefers-*) | Нет (только размеры) |
| Performance | Нативно, без overhead | CSS Containment под капотом — быстрее JS-полифиллов |
Правило большого пальца: медиазапросы — для структуры страницы, контейнерные запросы — для компонентов внутри неё.
Viewport (Media Queries)
└── Sidebar (Container Query)
│ └── Card (адаптируется к ширине сайдбара)
└── Main content (Container Query)
└── Grid (адаптируется к ширине контента)
└── Card (тот же компонент, другая раскладка)
Реальные кейсы: где container queries меняют игру
Кейс 1. Карточка товара в трёх контекстах
Самый классический пример: карточка товара на e-commerce-странице.
<!-- Сайдбар "Недавно просмотренные" (≈350px) -->
<aside class="sidebar">
<div class="product-grid-cell">
<article class="product-card">...</article>
</div>
</aside>
<!-- Основная сетка (3 колонки, ≈400px на карточку) -->
<main>
<div class="product-grid-cell">
<article class="product-card">...</article>
</div>
</main>
<!-- Hero-секция (полная ширина, ≈1200px) -->
<section class="hero">
<div class="product-grid-cell">
<article class="product-card">...</article>
</div>
</section>
.product-grid-cell {
container: product-card / inline-size;
}
/* Базовый — вертикальная раскладка (сайдбар) */
.product-card {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
border-radius: 0.75rem;
}
/* Средний — горизонтальная раскладка (сетка) */
@container product-card (min-width: 380px) {
.product-card {
flex-direction: row;
}
.product-card__image {
width: 160px;
flex-shrink: 0;
}
.product-card__button {
display: inline-flex;
}
}
/* Широкий — максимум деталей (hero) */
@container product-card (min-width: 700px) {
.product-card__image {
width: 280px;
}
.product-card__description {
display: block; /* Скрыт по умолчанию */
}
.product-card__title {
font-size: clamp(1rem, 3cqi, 1.75rem);
}
}
Один и тот же компонент, один и тот же CSS, три разных раскладки — в зависимости от того, сколько места дал родитель. Без модификаторов, без JavaScript, без дублирования стилей.
Кейс 2. Адаптивная навигация
Навигационное меню, которое переключается между горизонтальной и вертикальной раскладкой в зависимости от доступной ширины контейнера — не экрана.
.nav-container {
container: nav / inline-size;
}
.nav {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
/* Когда есть место — горизонтальная навигация */
@container nav (min-width: 500px) {
.nav {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.nav__item {
display: inline-flex;
}
}
/* Когда места совсем мало — скрываем текст, оставляем иконки */
@container nav (max-width: 200px) {
.nav__label {
display: none;
}
.nav__item {
justify-content: center;
}
}
Такое меню работает одинаково корректно и в полную ширину хедера, и в узком сайдбаре, и внутри виджета дашборда.
Кейс 3. Дашборд-виджеты
В дашборде один и тот же виджет может занимать разную долю сетки — целую строку, половину, треть. Контейнерные запросы позволяют виджету менять плотность информации.
.widget-cell {
container: widget / inline-size;
}
.widget {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
padding: 1.5rem;
}
/* Компактный режим — меньше отступов, скрытые детали */
@container widget (max-width: 350px) {
.widget {
padding: 0.75rem;
gap: 0.5rem;
}
.widget__description {
display: none;
}
.widget__chart {
height: 120px;
}
}
/* Полный режим — расширенная визуализация */
@container widget (min-width: 600px) {
.widget {
grid-template-columns: 1fr 2fr;
}
.widget__chart {
height: 300px;
}
.widget__legend {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
Кейс 4. Адаптивная таблица → карточки
Таблица, которая превращается в набор карточек, когда контейнер становится слишком узким — без привязки к viewport.
.table-container {
container-type: inline-size;
}
/* Базовая — табличный вид */
table, thead, tbody, tr, th, td {
display: revert;
}
/* Узкий контейнер — карточки */
@container (max-width: 600px) {
table, thead, tbody, tr, th, td {
display: block;
}
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
tr {
border-bottom: 1px solid #ccc;
padding: 8px 0;
}
td::before {
content: attr(data-label);
font-weight: bold;
margin-right: 8px;
}
}
<div class="table-container">
<table>
<thead>
<tr>
<th>Имя</th>
<th>Email</th>
<th>Должность</th>
<th>Зарплата</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Имя">Иван Петров</td>
<td data-label="Email">ivan@example.com</td>
<td data-label="Должность">Frontend-разработчик</td>
<td data-label="Зарплата">280 000 ₽</td>
</tr>
</tbody>
</table>
</div>
Кейс 5. Календарь: комбинация media + container
Самые интересные результаты даёт комбинация обоих подходов. Медиазапросы управляют общей структурой страницы, контейнерные — деталями компонента.
/* Media query — глобальная раскладка */
@media (max-width: 768px) {
.page-layout {
grid-template-columns: 1fr;
}
}
@media (min-width: 769px) {
.page-layout {
grid-template-columns: 300px 1fr;
}
}
/* Container query — компонент календаря */
.calendar-wrapper {
container: calendar / inline-size;
}
@container calendar (min-width: 500px) {
.calendar__day {
display: grid;
grid-template-columns: 60px 1fr;
align-items: start;
}
.calendar__events {
flex-direction: row;
flex-wrap: wrap;
}
}
@container calendar (max-width: 499px) {
.calendar__day {
display: flex;
flex-direction: column;
}
.calendar__events {
flex-direction: column;
}
.calendar__event-time {
font-size: 0.75rem;
}
}
Container Style Queries: Следующий шаг
Помимо размерных запросов, спецификация включает style queries — запросы по вычисленным стилям контейнера. Чаще всего это запросы к CSS-переменным:
@container style(--card-variant: compact) {
.card-body {
padding: 8px;
font-size: 0.875rem;
}
}
@container style(--theme: dark) {
.card {
background: #1a1a2e;
color: #e0e0e0;
}
}
Родитель устанавливает переменную — дочерний компонент реагирует. Это открывает паттерн тематирования без переключения классов через JavaScript.
Поддержка style queries пока ограничена: Chrome и Edge поддерживают, Firefox и Safari работают над полной реализацией. Для production стоит проверять через @supports.
Поддержка браузеров: 2026
| Возможность | Chrome | Firefox | Safari | Edge | Глобальная поддержка |
|---|---|---|---|---|---|
@container (size) | 105+ | 110+ | 16+ | 105+ | ~93–95% |
Container units (cqw, cqi...) | 105+ | 110+ | 16+ | 105+ | ~93–95% |
Style queries (@container style()) | Да | В разработке | В разработке | Да | Ограниченная |
| Scroll-state queries | 135+ | Нет | Нет | 135+ | Частичная |
Размерные контейнерные запросы и единицы измерения получили статус Baseline (widely available) в начале 2024 года и поддерживаются во всех основных браузерах. Для production-проектов в 2026 году полифиллы, как правило, не нужны.
Стратегия fallback проста: базовые стили (вне @container-правил) должны быть narrow/mobile-вариантом. Если браузер не поддерживает container queries — компонент получит мобильную раскладку, что почти всегда приемлемо.
Миграция: с чего начать
1. Аудит компонентов
Найдите компоненты, которые используют классы-модификаторы для разных контекстов (--sidebar, --compact, --horizontal). Это первые кандидаты на миграцию.
2. Замена модификаторов на контейнерные запросы
/* Было: модификаторы + media queries */
.card { /* vertical */ }
.card--horizontal { display: flex; flex-direction: row; }
@media (min-width: 768px) {
.card { flex-direction: column; }
}
/* Стало: container queries */
.card-wrapper { container: card / inline-size; }
.card { /* vertical — базовая */ }
@container card (min-width: 400px) {
.card { flex-direction: row; }
}
3. Сохранение media queries для макета
Не пытайтесь переписать всё. Медиазапросы остаются для:
- глобальной сетки страницы
- пользовательских предпочтений (
prefers-*) - адаптации шрифтов на уровне страницы
4. Progressive enhancement через @supports
/* Fallback для старых браузеров */
.feature-card {
text-align: center;
padding: 1rem;
}
/* Усиление для современных */
@supports (container-type: inline-size) {
.feature-card-wrapper {
container-type: inline-size;
container-name: feature;
}
@container feature (min-width: 400px) {
.feature-card {
display: flex;
text-align: left;
gap: 1rem;
}
}
}
5. Производительность
Container queries построены на CSS Containment API — браузер изолирует поддерево, ограничивая область пересчёта стилей. Тесты в Chrome DevTools показывают, что нативные container queries обходят JavaScript-аналоги на ResizeObserver на 20–30% по времени repaint. Несколько рекомендаций:
- Используйте
inline-size, а неsize, если не нужна реакция на высоту. - Именуйте контейнеры в сложных вложенных структурах — это снижает overhead на матчинг запросов.
- Для плавного масштабирования предпочтите
cqi-единицы каскаду брейкпоинтов.
Итог
Media queries ответили на вопрос «какой размер у экрана?». Container queries отвечают на более полезный вопрос: «сколько места у этого компонента?».
К 2026 году контейнерные запросы — не эксперимент, а production-ready инструмент с поддержкой более 93% браузеров. Tailwind CSS v4 добавил встроенные утилиты для container queries без плагинов — это сигнал, что технология перешла из статуса «интересная новинка CSS» в категорию повсеместно применяемых инструментов.
Архитектурный сдвиг значителен: компоненты становятся по-настоящему независимыми. Карточка, виджет, таблица, навигация — каждый элемент несёт собственную адаптивную логику и работает корректно в любом контейнере, от узкого сайдбара до full-width hero-секции. Это не просто удобство — это устранение целого класса багов, которые мы годами закрывали модификаторами и JavaScript-обвязками.
Медиазапросы всё ещё актуальны. Они лучше всего подходят для макроуровня: создания структуры страницы, настройки пользователя и параметров устройства. В 2026 году идеальный адаптив — это двухслойная архитектура: медиазапросы управляют всей страницей, а контейнерные запросы — компонентами внутри неё.