Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «CSS запити контейнерів». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**CSS container queries** (контейнерні запити) стилізують елементи на основі розміру батьківського контейнера, а не viewport. ```css .parent { container-type: inline-size; } @container (min-width: 400px) { .card { display: flex; flex-direction: row; } } ``` **Ключове:** `@media` перевіряє ширину браузера. `@container` перевіряє ширину батьківського елемента. Один компонент адаптується в будь-якому макеті без перевизначень для кожного контексту.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**CSS container queries** (контейнерні запити) дозволяють стилізувати елементи на основі розміру батьківського контейнера, а не вікна браузера. ## Теорія ### TL;DR - Media queries стежать за розміром вікна браузера; container queries — за розміром батьківського елемента. - Аналогія: media queries як центральна система опалення (весь будинок реагує однаково); container queries як особистий термостат (кожен пристрій адаптується до свого місця). - Без `container-type: inline-size` на батьківському елементі `@container` нічого не зробить. - Container queries для компонентів, які повторно використовуються; media queries для глобальних макетів сторінки. - Підтримка браузерами: Chrome 105+, Firefox 110+, Safari 16+. ### Швидкий приклад Два контейнери, різна ширина. Той самий `.card` всередині кожного. Тільки широкий активує зміну макету: ```css .parent { container-type: inline-size; /* оголошуємо контейнер для запитів */ } @container (min-width: 400px) { .card { display: flex; flex-direction: row; /* горизонтально тільки у широких контейнерах */ } } ``` ```html <div class="parent" style="width: 300px"> <div class="card">Вузький: залишається вертикальним</div> </div> <div class="parent" style="width: 500px"> <div class="card">Широкий: стає горизонтальним</div> </div> ``` Картка у 300px контейнері залишається вертикальною. Картка у 500px контейнері перемикається на горизонтальний макет. Розмір вікна браузера тут не має значення. ### Ключова різниця `@media` перевіряє розміри `window` через `matchMedia()`. Уся сторінка ділить один viewport, тому кожен компонент на `@media` прив'язаний до контексту сторінки. `@container` працює на рівні окремого елемента: браузер створює список запитів для кожного оголошеного контейнера і обчислює їх незалежно. Той самий `.card` може бути вузьким у бічній панелі і широким у hero-секції без жодного додаткового CSS. ### Коли що використовувати - Картка, яка з'являється і в бічній панелі, і в основній сітці: container queries (кожен адаптується локально). - Віджети дашборду різної ширини: container queries (кожен обчислюється незалежно). - Навігація, яка згортається на мобільному: media queries (це глобальне рішення для макету). - Стилі для друку або орієнтації екрана: media queries (рівень пристрою, не компонента). Найпростіше правило: якщо компонент використовується в різних макетах, container queries. Якщо змінюєш структуру самої сторінки, media queries. ### Таблиця порівняння | Ознака | Media Queries | Container Queries | |---|---|---| | Тригер | Розмір viewport (браузера) | Розмір батьківського контейнера | | Синтаксис | `@media (min-width: 768px)` | `@container (min-width: 400px)` | | Область дії | Глобальна (вся сторінка) | Локальна (окремий контейнер) | | Підтримка браузерами | IE9+ | Chrome 105+, Firefox 110+, Safari 16+ | | Найкраще для | Макети сторінок, теми, друк | Компоненти в сітках і flex-макетах | ### Як це працює всередині Браузер створює об'єкт `ContainerQuery` для кожного елемента з оголошеним `container-type`. Під капотом механізм схожий на `ResizeObserver`: коли inline або block розмір контейнера змінюється, рушій перераховує список правил `@container` і оновлює стилі. Якщо нічого не змінилось, reflow не відбувається. Контейнер також встановлює контекст ізоляції (containment context), який відокремлює піддерево від батьківського макету до того, як запит спрацьовує. Саме тому контейнер не може запитувати власний розмір: специфікація запобігає циклічній залежності на межі containment. ### Одиниці вимірювання контейнерних запитів Container queries мають власні одиниці довжини: - `cqw` — 1% ширини контейнера - `cqh` — 1% висоти контейнера - `cqi` — 1% inline розміру контейнера - `cqb` — 1% block розміру контейнера - `cqmin` / `cqmax` — менше або більше з `cqi` і `cqb` ```css .card-container { container-type: inline-size; } .card { font-size: clamp(1rem, 4cqw, 2rem); /* масштабується з контейнером, не з viewport */ padding: clamp(1rem, 5cqw, 3rem); } ``` Для компонентів всередині інших елементів це передбачуваніше ніж одиниці `vw`. ### Іменовані контейнери Якщо на сторінці кілька контейнерів, дай їм імена щоб контролювати, який `@container` запит до якого відноситься: ```css .sidebar { container-type: inline-size; container-name: sidebar; } .main-content { container-type: inline-size; container-name: main; } @container sidebar (min-width: 300px) { .card { /* спрацьовує тільки всередині .sidebar */ } } @container main (min-width: 600px) { .card { /* спрацьовує тільки всередині .main-content */ } } ``` Без імені `@container` запитує найближчого предка з `container-type`. ### Типові помилки **Забули `container-type` на батьківському елементі.** Найпоширеніша проблема. Без оголошеного контейнера правила `@container` просто ігноруються. Ні помилки, ні попередження, просто нічого не відбувається. ```css /* неправильно: контейнер не оголошено, @container ігнорується */ @container (min-width: 400px) { .card { display: flex; } } /* правильно */ .parent { container-type: inline-size; } @container (min-width: 400px) { .card { display: flex; } } ``` **Використовуєш `block-size`, коли потрібен `inline-size`.** `container-type: block-size` працює, але потрібен рідко і має нерівномірну підтримку (Firefox додав у 121). У більшості випадків `inline-size` закриває потребу. Якщо треба запитувати обидва виміри, використовуй `container-type: size`. **Вкладені контейнери у flex/grid без ізоляції.** Якщо дочірній контейнер змінює розмір, він може ініціювати повторне обчислення батьківського контейнера, що знову змінює розмір дочірнього. Chrome 117+ додав `self-contained` для розриву цього циклу: ```css .inner-container { container-type: inline-size self-contained; } ``` **Запити до замалих контейнерів.** `@container (min-width: 300px)` на 20px іконці ніколи не спрацює. Перевір фактичну мінімальну ширину контейнера перед тим як писати проти нього запити. **Використовують style queries без `style` у `container-type`.** Контейнерні запити стилів (Chrome 111+) дозволяють запитувати значення кастомних властивостей, але `style` має бути у `container-type`: ```css .card-container { container-type: style inline-size; /* 'style' обов'язковий */ } @container style(--variant: dark) { .card { background: #111; color: #fff; } } ``` ### Де зустрічається в реальних проектах - **Shadcn/UI** — картки використовують `@container` щоб адаптуватись у будь-якому макеті без перевизначень для кожного контексту. - **Tailwind CSS v3.2+** — є утиліти для `container-type` і офіційний плагін для `@container`. - **Chakra UI** — тайли дашборду у flex-сітках використовують container queries щоб складатись або розгортатись залежно від ширини тайла. - **Headless UI** — вміст модальних вікон адаптується до ширини діалогу, а не viewport. Я почав використовувати container queries після того як витратив забагато часу на боротьбу з `@media` брейкпоінтами в бібліотеці компонентів: картки з'являлись одночасно в бічних панелях, сітках і повноширинних банерах. Один набір правил `@container` замінив три набори макет-специфічних перевизначень. ### Питання для співбесіди **Q:** Яка різниця між `container-type: inline-size` і `size`? **A:** `inline-size` відстежує тільки ширину. `size` відстежує і ширину, і висоту. Використовуй `size` тільки коли запит має реагувати на вертикальні зміни теж, бо він створює сильніші обмеження containment. **Q:** Чому контейнер не може запитувати власний розмір? **A:** Виникає циклічна залежність: розмір елемента залежить від дочірніх, які залежать від результату запиту, який залежить від розміру елемента. Специфікація розриває цей цикл, вимагаючи встановлення containment до обчислення запиту. **Q:** Як container queries взаємодіють з CSS containment? **A:** Оголошення `container-type` неявно додає `contain: layout style` (або `contain: layout style size` для типу `size`). Це ізолює піддерево і запобігає зсуву батьківського макету при зміні дочірніх елементів. **Q:** Чи є поліфіл? **A:** Повного поліфілу немає. Фіча спирається на `ResizeObserver` на рівні браузерного рушія. Обхідний шлях: JS `ResizeObserver` що додає і видаляє класи, і CSS-правила на основі класів як запасний варіант. **Q:** Продуктивність: 100 container queries проти 100 media queries? **A:** Обидва механізми використовують observer-based обчислення. На практиці 100+ контейнерів означає 100+ спостерігачів. Поєднуй з `content-visibility: auto` для позаекранних секцій щоб обмежити кількість активних спостерігачів. **Q (senior):** У сітці з 50 однакових карток, як зменшити вартість обчислення container queries підтримуючи при цьому style queries? **A:** Оголоси `container-type: style size self-contained` на спільному предку замість кожної картки окремо. Один спостерігач покриває предка. Картки читають кастомну властивість `--theme` предка через style query. За трасами Chromium DevTools це скорочує з 50 незалежних спостерігачів до одного на групу. ## Приклади ### Дві картки, один компонент, різні контейнери Той самий компонент, той самий CSS, різний результат залежно від ширини батька. ```html <style> .parent { container-type: inline-size; border: 1px solid #ccc; margin: 20px; padding: 10px; } .narrow { width: 300px; } .wide { width: 560px; } .card { background: #e8f4ff; padding: 16px; display: flex; flex-direction: column; /* за замовчуванням: вертикально */ gap: 12px; } @container (min-width: 450px) { .card { flex-direction: row; /* горизонтально у широких контейнерах */ align-items: center; } } </style> <div class="parent narrow"> <div class="card"> <img src="avatar.jpg" width="48" height="48" alt="Користувач"> <div> <strong>Ганна Ковальчук</strong> <p>Product designer</p> </div> </div> </div> <div class="parent wide"> <div class="card"> <img src="avatar.jpg" width="48" height="48" alt="Користувач"> <div> <strong>Ганна Ковальчук</strong> <p>Product designer</p> </div> </div> </div> ``` Вузький батько (300px): зображення над текстом. Широкий батько (560px): зображення ліворуч, текст праворуч. Розмір вікна браузера не задіяний. ### Віджет дашборду з іменованим контейнером і одиницями `cqi` Віджет, який з'являється і в слоті бічної панелі (240px), і в повноширинній зоні (900px). Без перевизначень для кожного контексту. ```jsx // DashboardCard.jsx function DashboardCard({ title, value, trend }) { return ( <div className="widget"> <div className="chart"> <h3 className="chart__title">{title}</h3> <span className="chart__metric">{value}</span> <span className="chart__trend">{trend}</span> </div> </div> ); } ``` ```css .widget { container-type: inline-size; container-name: dashboard-card; } .chart { padding: 16px; display: grid; grid-template-columns: 1fr; /* одна колонка за замовчуванням */ gap: 8px; } .chart__metric { font-size: clamp(1.25rem, 6cqi, 2.5rem); /* fluid, прив'язаний до ширини контейнера */ } @container dashboard-card (min-width: 450px) { .chart { grid-template-columns: 1fr 2fr; /* графік і деталі поруч */ } } @container dashboard-card (min-width: 600px) { .chart__metric { font-size: 2.5rem; } } ``` Одиниця `cqi` масштабує текст метрики відносно inline розміру контейнера, а не viewport. Зміни ширину слоту і текст масштабується разом з ним. ### Style queries для тематичних компонентів Style queries (Chrome 111+) дозволяють дочірнім елементам читати кастомні властивості з контейнера. Без передачі пропсів через кожен шар розмітки. ```css .card-container { container-type: style inline-size; /* 'style' вмикає style queries */ container-name: card-wrapper; } .card { background: #fff; color: #111; padding: 20px; border-radius: 8px; } @container card-wrapper style(--variant: dark) { .card { background: #1a1a2e; color: #e0e0e0; } } @container card-wrapper style(--variant: warning) { .card { background: #fff3cd; color: #856404; } } ``` ```html <div class="card-container" style="--variant: dark"> <div class="card">Картка з темною темою</div> </div> <div class="card-container" style="--variant: warning"> <div class="card">Картка попередження</div> </div> <div class="card-container"> <div class="card">Картка за замовчуванням</div> </div> ``` Кастомна властивість `--variant` на контейнері слугує сигналом. Картка читає його без жодного JS. Якщо пропустити `style` у `container-type`, запит ніколи не спрацює і всі картки показуватимуть стилі за замовчуванням.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.