Пошук уроків, статей та іншого контенту
Створите компоненти, що реагують на ширину власного контейнера, використовуючи container-type і container queries.
Звичайні media queries реагують на розмір viewport — області перегляду браузера:
@media (min-width: 768px) {
/* Стилі залежать від ширини вікна браузера */
}Але компонент може опинитися:
у широкій основній колонці;
у вузькому сайдбарі;
у CSS Grid;
у CSS Flexbox;
у модальному вікні;
у вкладці або віджеті з обмеженою шириною.
В усіх цих випадках viewport може залишатися незмінним, а доступна компоненту ширина — змінюватися.
Container queries дають змогу стилізувати компонент залежно від розміру його батьківського контейнера, а не всього viewport.
@container (min-width: 40rem) {
.card {
/* Стилі застосовуються, якщо контейнер достатньо широкий */
}
}Щоб запит працював, потрібно:
оголосити елемент контейнером запитів;
вказати, який розмір цього контейнера контролюється;
описати стилі в @container.
Для створення контейнера використовується властивість container-type:
.wrapper {
container-type: inline-size;
}Значення inline-size означає, що нащадки можуть реагувати на розмір контейнера вздовж inline-напрямку. Для типового горизонтального письма це ширина.
Найчастіше достатньо саме inline-size:
.component-container {
container-type: inline-size;
}Це дає змогу використовувати запити на кшталт:
@container (min-width: 30rem) {
.component {
display: grid;
}
}container-typecontainer-type може мати такі основні значення:
normal — елемент не є контейнером для розмірних container queries;
inline-size — доступні запити за inline-розміром;
size — доступні запити за inline- та block-розміром.
Для адаптивних компонентів, які змінюють розкладку залежно від ширини, використовуйте:
.container {
container-type: inline-size;
}Значення size потрібне лише тоді, коли компонент має реагувати також на висоту контейнера:
.container {
container-type: size;
}
@container (min-height: 30rem) {
.component {
/* Стилі залежать від висоти контейнера */
}
}У більшості компонентів перевірка висоти не потрібна. inline-size має менший вплив на розрахунок розмітки та краще відповідає типовій задачі адаптації за шириною.
Після оголошення контейнера можна використовувати правило @container:
.card-container {
container-type: inline-size;
}
@container (min-width: 36rem) {
.card {
grid-template-columns: 12rem 1fr;
}
}Правило всередині @container застосовується до нащадків контейнера, якщо контейнер відповідає умові.
Наприклад:
<section class="card-container">
<article class="card">
<h2>Назва матеріалу</h2>
<p>Короткий опис матеріалу.</p>
</article>
</section>.card-container {
container-type: inline-size;
}
.card {
display: grid;
gap: 1rem;
}
@container (min-width: 36rem) {
.card {
grid-template-columns: 12rem 1fr;
}
}На вузькому контейнері картка залишиться одноколонковою. Коли ширина контейнера досягне 36rem, картка перейде до двох колонок.
Якщо на сторінці є кілька вкладених контейнерів, краще давати їм імена. Ім’я вказується через container-name:
.layout {
container-name: layout;
container-type: inline-size;
}Після цього ім’я можна використати в @container:
@container layout (min-width: 60rem) {
.navigation {
display: flex;
}
}Синтаксис:
@container назва-контейнера умова {
/* Стилі нащадків контейнера */
}Ім’я контейнера може бути ідентифікатором CSS, наприклад:
.container {
container-name: product-card;
container-type: inline-size;
}У складених іменах використовуються дефіси, а не пробіли.
container-name і container-type можна об’єднати через скорочену властивість container:
.product-list {
container: product-list / inline-size;
}Це еквівалентно:
.product-list {
container-name: product-list;
container-type: inline-size;
}Для більшої наочності в навчальному або командному коді іноді зручніше залишати властивості окремо:
.product-list {
container-name: product-list;
container-type: inline-size;
}Якщо container query не має імені, браузер шукає найближчого предка, який є відповідним контейнером:
.outer {
container-type: inline-size;
}
.inner {
container-type: inline-size;
}
.component {
/* ... */
}
@container (min-width: 40rem) {
.component {
/* Буде перевірено найближчий відповідний контейнер */
}
}У цьому прикладі для .component перевірятиметься .inner, а не .outer.
Іменований запит дає змогу явно вибрати потрібний контейнер:
.outer {
container: page / inline-size;
}
.inner {
container: component / inline-size;
}
@container page (min-width: 60rem) {
.component {
/* Запит до контейнера page */
}
}Правило може застосовуватися до нащадка названого контейнера, навіть якщо між ними є інші елементи.
Нижче наведено повний приклад. Одна й та сама картка використовується у двох областях із різною шириною. Вона реагує саме на ширину власного контейнера.
<!doctype html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Container queries</title>
<style>
:root {
color-scheme: light;
font-family: system-ui, sans-serif;
line-height: 1.5;
background: #f1f5f9;
color: #172033;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 2rem;
}
.page {
max-width: 1100px;
margin: 0 auto;
}
.page-title {
margin: 0 0 1.5rem;
}
.content-layout {
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(15rem, 1fr);
gap: 1.5rem;
}
.column {
min-width: 0;
container-type: inline-size;
}
.column-title {
margin-top: 0;
}
.card {
display: grid;
gap: 1rem;
padding: 1rem;
border: 1px solid #cbd5e1;
border-radius: 0.75rem;
background: white;
}
.card__image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 0.5rem;
background: #bfdbfe;
}
.card__title {
margin: 0;
font-size: 1.25rem;
}
.card__description {
margin: 0;
color: #475569;
}
.card__actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.button {
padding: 0.6rem 0.9rem;
border: 0;
border-radius: 0.4rem;
background: #2563eb;
color: white;
font: inherit;
cursor: pointer;
}
.button--secondary {
background: #e2e8f0;
color: #172033;
}
@container (min-width: 36rem) {
.card {
grid-template-columns: minmax(10rem, 14rem) 1fr;
align-items: center;
}
.card__image {
grid-row: span 3;
height: 100%;
}
.card__actions {
justify-content: flex-start;
}
}
@container (min-width: 48rem) {
.card {
padding: 1.5rem;
gap: 1.5rem;
}
.card__title {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<main class="page">
<h1 class="page-title">Матеріали курсу</h1>
<div class="content-layout">
<section class="column">
<h2 class="column-title">Основний список</h2>
<article class="card">
<div class="card__image" aria-hidden="true"></div>
<h3 class="card__title">Container queries у CSS</h3>
<p class="card__description">
Компонент змінює розкладку залежно від ширини батьківського контейнера.
</p>
<div class="card__actions">
<button class="button" type="button">Відкрити</button>
<button class="button button--secondary" type="button">Зберегти</button>
</div>
</article>
</section>
<aside class="column">
<h2 class="column-title">Рекомендоване</h2>
<article class="card">
<div class="card__image" aria-hidden="true"></div>
<h3 class="card__title">Адаптивні компоненти</h3>
<p class="card__description">
Та сама картка працює у вужчому сайдбарі без media queries.
</p>
<div class="card__actions">
<button class="button" type="button">Відкрити</button>
</div>
</article>
</aside>
</div>
</main>
</body>
</html>У прикладі обидва елементи .column є контейнерами:
.column {
container-type: inline-size;
}Картка не знає, де саме вона використовується. Вона лише реагує на ширину найближчого контейнера:
у широкій основній колонці переходить до двох колонок;
у вузькому сайдбарі залишається вертикальною;
при зміні розміру колонок перебудовується незалежно від ширини viewport.
Це робить компонент повторно використовуваним у різних частинах інтерфейсу.
Container queries підтримують умови, подібні до media queries:
@container (min-width: 30rem) {
.component {
/* Мінімальна ширина контейнера */
}
}
@container (max-width: 29.99rem) {
.component {
/* Максимальна ширина контейнера */
}
}Можна поєднувати умови:
@container (min-width: 30rem) and (max-width: 60rem) {
.component {
/* Стилі для проміжного діапазону */
}
}Також підтримується логічний оператор or:
@container (max-width: 24rem) or (min-width: 70rem) {
.component {
/* Стилі для дуже вузького або дуже широкого контейнера */
}
}На практиці краще починати з базового стилю для вузького контейнера, а потім додавати зміни через min-width:
.card {
display: block;
}
@container (min-width: 36rem) {
.card {
display: grid;
}
}Такий підхід нагадує mobile-first, але точкою відліку є не viewport, а сам компонент.
Разом із container queries можна використовувати одиниці, які залежать від розміру контейнера:
cqw — 1% ширини контейнера;
cqh — 1% висоти контейнера;
cqi — 1% inline-розміру контейнера;
cqb — 1% block-розміру контейнера;
cqmin — менше значення з cqi і cqb;
cqmax — більше значення з cqi і cqb.
Для компонентів, що мають працювати в різних напрямках письма, зазвичай корисніша одиниця cqi:
.component {
container-type: inline-size;
padding-inline: clamp(1rem, 4cqi, 3rem);
}У цьому прикладі горизонтальні внутрішні відступи залежать від inline-розміру контейнера, але обмежуються через clamp().
Не варто без потреби замінювати всі rem або px на контейнерні одиниці. Container queries призначені насамперед для зміни структури компонента, а не для автоматичної зміни кожного розміру.
Запит перевіряє контейнер-предок, а стилі застосовуються до його нащадків:
.wrapper {
container-type: inline-size;
}
.component {
/* Нащадок контейнера */
}
@container (min-width: 40rem) {
.component {
/* Працює */
}
}Не можна оголосити сам компонент контейнером і водночас очікувати, що він стилізуватиме себе на основі власного розміру:
.component {
container-type: inline-size;
}
/* Так не слід проєктувати компонент */
@container (min-width: 40rem) {
.component {
/* Компонент не є нащадком самого себе */
}
}У такій ситуації контейнером потрібно зробити обгортку:
<div class="component-container">
<article class="component"></article>
</div>.component-container {
container-type: inline-size;
}min-width: 0У CSS Grid або Flexbox дочірній елемент може мати мінімальний розмір, що не дає йому коректно стискатися. Для контейнерів у колонках часто потрібне:
.column {
min-width: 0;
container-type: inline-size;
}Це не є частиною синтаксису container queries, але допомагає контейнеру реально отримувати доступну ширину в складній розкладці.
Поріг 36rem не зобов’язаний відповідати типовому breakpoint для viewport. Він має визначати момент, коли компоненту стає достатньо місця для іншої структури.
Наприклад:
до 30rem кнопки розташовані вертикально;
від 30rem вони розміщуються в ряд;
від 48rem зображення та текст розташовані поруч.
Такі пороги називають контентними breakpoint’ами. Вони залежать від потреб компонента, а не від конкретних пристроїв.
Ці механізми вирішують різні задачі.
Використовуйте media queries, коли потрібно реагувати на властивості viewport:
@media (prefers-reduced-motion: reduce) {
.animated-element {
animation: none;
}
}Використовуйте container queries, коли потрібно змінити внутрішню розкладку компонента залежно від доступного йому простору:
@container (min-width: 40rem) {
.card {
grid-template-columns: 1fr 1fr;
}
}Один компонент може використовувати обидва механізми, але кожен запит має відповідати своїй задачі.
Базові стилі варто писати так, щоб компонент залишався придатним до використання без container queries. Потім ширший варіант можна додати всередині @container:
.card {
display: block;
}
@container (min-width: 36rem) {
.card {
display: grid;
grid-template-columns: 12rem 1fr;
}
}За потреби підтримку функції можна перевірити через @supports:
@supports (container-type: inline-size) {
.component-container {
container-type: inline-size;
}
@container (min-width: 36rem) {
.component {
display: grid;
}
}
}Однак основний принцип залишається тим самим: спочатку визначте надійну базову розкладку, а container query використовуйте для додаткової адаптації.
Правило @container саме по собі не створює контейнер:
@container (min-width: 40rem) {
.card {
display: grid;
}
}Перед ним має бути контейнер-предок:
.card-list {
container-type: inline-size;
}Якщо ближче до компонента є інший контейнер, саме він буде перевірятися для неназваного запиту. Якщо потрібен конкретний предок, використовуйте container-name.
.page {
container: page / inline-size;
}
.sidebar {
container: sidebar / inline-size;
}
@container page (min-width: 60rem) {
.component {
/* Явно вибрано контейнер page */
}
}У container query значення min-width: 40rem означає мінімальну ширину контейнера, а не браузера:
@container (min-width: 40rem) {
.card {
/* 40rem — ширина контейнера */
}
}Для viewport використовується @media, для контейнера — @container.
Компонент не може застосувати до себе правило через власний container query. Запит має бути оголошений для нащадка контейнера, тому потрібна обгортка.
size без потреби.container {
container-type: size;
}size вмикає containment і для ширини, і для висоти. Якщо компоненту потрібна лише адаптація за шириною, використовуйте:
.container {
container-type: inline-size;
}Одиниці на кшталт cqi призначені для роботи в контексті контейнерних запитів. Спочатку переконайтеся, що предок компонента має відповідний container-type.
Під час створення адаптивного компонента дійте так:
Визначте обгортку, ширина якої справді впливає на компонент.
Додайте їй container-type: inline-size.
Напишіть базову розкладку для вузького контейнера.
Знайдіть момент, коли вмісту стає достатньо місця для нової структури.
Додайте @container (min-width: ...).
Якщо контейнерів кілька або вони вкладені, використайте імена.
Перевірте компонент у контейнерах різної ширини, а не лише у вікні різного розміру.
Container queries реагують на розмір контейнера, а не viewport.
Для запитів за шириною використовуйте container-type: inline-size.
Стилі описуються через @container.
Іменовані контейнери створюються за допомогою container-name або скорочення container.
Неназваний запит використовує найближчий відповідний контейнер-предок.
Компонент не може стилізувати себе через власний container query.
Пороги container queries потрібно вибирати за потребами контенту.
cqi, cqw та інші контейнерні одиниці дають змогу масштабувати значення відносно контейнера.
Найкраще починати з базової розкладки для вузького контейнера, а ширші варіанти додавати через min-width.