Container queries
Container queries (
@container) позволяют адаптировать стили элемента в зависимости от размера его контейнера, а не viewport (как в media queries). Это делает компоненты по-настоящему автономными и переиспользуемыми.
Зачем нужно
Media queries отвечают на вопрос «какой размер окна?». Но компонент может находиться в сайдбаре (300px), в основном контенте (800px) или в модальном окне (500px) — и нужны разные стили для каждого случая. Container queries решают эту проблему, делая компоненты по-настоящему переиспользуемыми.
Где используется
- Адаптивные карточки, которые меняют layout в зависимости от контейнера
- Компоненты дизайн-систем (переиспользование в разных контекстах)
- Sidebar-виджеты, которые адаптируются к ширине сайдбара
- Вложенные grid/flex элементы
- Micro-frontends и Web Components
Предпосылки
- Media queries — viewport-зависимые запросы
- Responsive Design -- принципы — принципы адаптивности
- Блочная модель — размеры элементов
- Основы Grid или Flexbox — раскладка
Базовый синтаксис
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; }
}
Частые ошибки
- Забыли
container-type— без него@containerне работает:/* ОШИБКА — нет container-type на родителе */ @container (min-width: 500px) { } /* ПРАВИЛЬНО */ .parent { container-type: inline-size; } - Запрос к собственным размерам — элемент не может быть контейнером и запрашивать свой размер:
/* ОШИБКА — .card не может быть контейнером для себя */ .card { container-type: inline-size; } @container (min-width: 500px) { .card { /* Не сработает для самого .card! */ } } /* Нужен отдельный контейнер-обёртка */ container-type: sizeбез явной высоты — элемент схлопывается, т.к. размер зависит от содержимого, но containment это запрещает- Container query на самом контейнере —
@containerприменяется к ДЕТЯМ контейнера, не к самому контейнеру - Слишком много брейкпоинтов — 2-3 достаточно для большинства компонентов
- Слишком много вложенных контейнеров — усложняет дебаг. Используйте имена
Практика
- Создать контейнер и применить
@containerдля карточки - Сделать карточку с 3 вариантами layout (маленькая, средняя, большая)
- Использовать именованные контейнеры для sidebar и main
- Попробовать единицы
cqiдля fluid typography - Комбинировать media queries (макет) и container queries (компоненты)
- Поэкспериментировать со style queries (CSS-переменные)
Связанные темы
- Media queries — viewport-зависимые запросы
- Responsive Design -- принципы — принципы адаптивного дизайна
- Основы Grid — grid внутри container queries
- Flexbox vs Grid — выбор раскладки в зависимости от размера
- min() max() clamp() — адаптивные размеры
- Viewport units — единицы вьюпорта
- calc() — вычисления