Container queries

Container queries (@container) позволяют адаптировать стили элемента в зависимости от размера его контейнера, а не viewport (как в media queries). Это делает компоненты по-настоящему автономными и переиспользуемыми.

Зачем нужно

Media queries отвечают на вопрос «какой размер окна?». Но компонент может находиться в сайдбаре (300px), в основном контенте (800px) или в модальном окне (500px) — и нужны разные стили для каждого случая. Container queries решают эту проблему, делая компоненты по-настоящему переиспользуемыми.

Где используется

  • Адаптивные карточки, которые меняют layout в зависимости от контейнера
  • Компоненты дизайн-систем (переиспользование в разных контекстах)
  • Sidebar-виджеты, которые адаптируются к ширине сайдбара
  • Вложенные grid/flex элементы
  • Micro-frontends и Web Components

Предпосылки

Базовый синтаксис

1. Определить контейнер

.card-container {
  container-type: inline-size;
  /* Элемент становится контейнером для container queries */
}

2. Использовать @container

.card {
  display: grid;
  gap: 12px;
}

/* Если контейнер шире 500px — горизонтальный layout */
@container (min-width: 500px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
}

/* Если контейнер шире 700px — ещё шире */
@container (min-width: 700px) {
  .card {
    grid-template-columns: 300px 1fr;
    gap: 24px;
  }
}

container-type

/* Отслеживать ширину (inline-size) — самый распространённый */
.container {
  container-type: inline-size;
}

/* Отслеживать ширину и высоту */
.container {
  container-type: size;
}

/* Не является контейнером (по умолчанию) */
.container {
  container-type: normal;
}

inline-size — самый частый. size нужен редко и может ломать auto-sizing, т.к. требует явную высоту.

container-name

Именованные контейнеры позволяют точно указать, к какому контейнеру привязан запрос:

.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.main-content {
  container-type: inline-size;
  container-name: main;
}

/* Запрос к конкретному контейнеру */
@container sidebar (min-width: 300px) {
  .widget {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

@container main (min-width: 600px) {
  .article-card {
    grid-template-columns: 250px 1fr;
  }
}

Сокращённое свойство container

.sidebar {
  container: sidebar / inline-size;
  /* Эквивалент: */
  /* container-name: sidebar; */
  /* container-type: inline-size; */
}

Синтаксис @container

/* min-width */
@container (min-width: 500px) {
  .card { flex-direction: row; }
}

/* max-width */
@container (max-width: 400px) {
  .card { font-size: 0.875rem; }
}

/* Range syntax */
@container (300px <= width <= 600px) {
  .card { padding: 16px; }
}

/* По имени */
@container card-wrapper (min-width: 500px) {
  .card { display: grid; }
}

/* Комбинация условий */
@container (min-width: 400px) and (max-width: 799px) {
  .card { grid-template-columns: 1fr 1fr; }
}

Container query units

Единицы, относительные к размеру контейнера:

.card-container {
  container-type: inline-size;
}

.card-title {
  /* cqw — 1% ширины контейнера */
  font-size: clamp(1rem, 3cqw, 2rem);
}

.card-image {
  /* cqh — 1% высоты контейнера (нужен container-type: size) */
  height: 30cqh;
}
Единица Значение
cqw 1% ширины контейнера
cqh 1% высоты контейнера
cqi 1% inline-size контейнера
cqb 1% block-size контейнера
cqmin Меньшее из cqi и cqb
cqmax Большее из cqi и cqb

Практические примеры

Адаптивная карточка

<div class="card-wrapper">
  <article class="card">
    <img class="card-img" src="photo.jpg" alt="">
    <div class="card-content">
      <h3 class="card-title">Заголовок</h3>
      <p class="card-text">Описание карточки...</p>
      <button class="card-btn">Подробнее</button>
    </div>
  </article>
</div>
.card-wrapper {
  container-type: inline-size;
}

/* Маленький контейнер — вертикальная карточка */
.card {
  display: grid;
  border-radius: 12px;
  overflow: hidden;
  background: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.card-img {
  width: 100%;
  aspect-ratio: 16/9;
  object-fit: cover;
}

.card-content {
  padding: 16px;
}

.card-title {
  font-size: 1.125rem;
}

/* Средний контейнер — горизонтальная */
@container (min-width: 400px) {
  .card {
    grid-template-columns: 180px 1fr;
  }

  .card-img {
    aspect-ratio: 1;
    height: 100%;
  }
}

/* Большой контейнер — расширенная */
@container (min-width: 600px) {
  .card {
    grid-template-columns: 250px 1fr;
  }

  .card-title {
    font-size: 1.5rem;
  }

  .card-content {
    padding: 24px;
  }
}

Виджет в разных зонах

.sidebar {
  container: sidebar / inline-size;
}

.main-content {
  container: main / inline-size;
}

/* Базовый виджет */
.widget {
  padding: 12px;
  border: 1px solid #dee2e6;
  border-radius: 8px;
}

.widget-stats {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* В сайдбаре шире 250px */
@container sidebar (min-width: 250px) {
  .widget-stats {
    flex-direction: row;
    flex-wrap: wrap;
  }

  .widget-stats > * {
    flex: 1 1 45%;
  }
}

/* В основном контенте шире 600px */
@container main (min-width: 600px) {
  .widget-stats {
    flex-direction: row;
  }

  .widget-stats > * {
    flex: 1;
  }
}

Style queries (экспериментально)

/* Запрос к значению CSS-переменной */
@container style(--theme: dark) {
  .card {
    background: #1a1a2e;
    color: #e9ecef;
  }
}

@container style(--layout: compact) {
  .card {
    padding: 8px;
    gap: 4px;
  }
}

Style queries имеют ограниченную поддержку (2026). Проверяйте caniuse.com перед использованием.

Container queries vs Media queries

Аспект Media queries Container queries
Зависит от Viewport (окно) Контейнера-родителя
Переиспользуемость Компонент привязан к viewport Компонент адаптируется к контексту
Вложенность Глобальные Локальные
Поддержка Все браузеры Современные браузеры

Комбинация обоих подходов

/* Media query — для макета страницы */
@media (max-width: 768px) {
  .sidebar { display: none; }
  .content { grid-column: 1 / -1; }
}

/* Container query — для компонента */
.content {
  container-type: inline-size;
}

@container (min-width: 500px) {
  .card { grid-template-columns: 200px 1fr; }
}

Частые ошибки

  1. Забыли container-type — без него @container не работает:
    /* ОШИБКА — нет container-type на родителе */
    @container (min-width: 500px) { }
    /* ПРАВИЛЬНО */
    .parent { container-type: inline-size; }
    
  2. Запрос к собственным размерам — элемент не может быть контейнером и запрашивать свой размер:
    /* ОШИБКА — .card не может быть контейнером для себя */
    .card { container-type: inline-size; }
    @container (min-width: 500px) {
      .card { /* Не сработает для самого .card! */ }
    }
    /* Нужен отдельный контейнер-обёртка */
    
  3. container-type: size без явной высоты — элемент схлопывается, т.к. размер зависит от содержимого, но containment это запрещает
  4. Container query на самом контейнере@container применяется к ДЕТЯМ контейнера, не к самому контейнеру
  5. Слишком много брейкпоинтов — 2-3 достаточно для большинства компонентов
  6. Слишком много вложенных контейнеров — усложняет дебаг. Используйте имена

Практика

  • Создать контейнер и применить @container для карточки
  • Сделать карточку с 3 вариантами layout (маленькая, средняя, большая)
  • Использовать именованные контейнеры для sidebar и main
  • Попробовать единицы cqi для fluid typography
  • Комбинировать media queries (макет) и container queries (компоненты)
  • Поэкспериментировать со style queries (CSS-переменные)

Связанные темы

Ресурсы