Практичний посібник з контейнерних запитів: компонентно-перший адаптивний дизайн

Було корисно?

Ви знаєте цю помилку. «Адаптивна» сітка карт виглядає ідеально на вашому ноутбуку, ломиться в боковій панелі
і перетворюється на трагічний витвір сучасного мистецтва всередині модального вікна. Хтось додає нову панель, і раптом
ваш «десктопний брейкпоінт» спрацьовує при ширині контейнера, схожому на мобільну, бо компонент опинився у дивному місці.

Контейнерні запити вирішують категорію збоїв, коли viewport не є правильним сигналом. У виробничих системах
правильним сигналом зазвичай є: «Скільки місця цей елемент фактично має прямо зараз?» Ось це обіцянка.
Реальність така: вам усе ще потрібно проєктувати й експлуатувати це відповідально.

Чому існують контейнерні запити (і коли вони не підходять)

Медіа-запити стосуються viewport. Це нормально, коли ваша розкладка здебільшого рівня сторінки:
хедер, основний вміст, бокова панель, футер. Це крихке, коли ваш інтерфейс — набір Lego:
картки всередині вкладок всередині висувного меню всередині сітки всередині сторінки.

Контейнерні запити дозволяють компоненту адаптуватися на основі розміру його контейнера, а не вікна.
Це означає, що «ProductCard» може рендеритись компактним рядком у вузькій боковій панелі й як
простора двоколонна розкладка у широкому контентному блоці — без потреби компонента знати,
де він розміщений.

Коли варто використовувати контейнерні запити:

  • Ви створюєте багаторазові компоненти, що використовуються в різних макетах (дашборди, блоки CMS, дизайн-системи).
  • Ваш макет включає змінювані панелі, розділені перегляди, бокові панелі, висувні меню та вкладені сітки.
  • Ви хочете, щоб ваші брейкпоінти описували стани компонента, а не класи пристроїв.

Коли не варто використовувати контейнерні запити:

  • Ви приймаєте справжні рішення рівня viewport (зміни глобальної навігації, масштабування типографіки на всю сторінку).
  • Ваш компонент ніколи не вбудовується і єдиним релевантним є viewport.
  • Ви можете вирішити задачу гнучкими макетами (grid/flex) взагалі без брейкпоінтів.

Оціночна думка: використовуйте медіа-запити для структури сторінки, контейнерні запити — для внутрішньої частини компонентів.
Якщо інвертувати це, ви побудуєте систему, яка «адаптується» так, як ніхто не просив.

Цікаві факти та коротка історія

Трохи контексту корисно, бо контейнерні запити виглядають як невелика функція CSS, але насправді
це зміна в моделі адаптивного UI. Ось конкретні факти, які варто пам’ятати:

  1. Контейнерні запити просили понад десять років, бо розробники хотіли модульні компоненти без глобальної прив’язки до брейкпоінтів.
  2. Складність була не в синтаксисі; а в уникненні циклів розкладки (стилі залежать від розміру; розмір залежить від стилів).
  3. Ранні поліміфи «element query» існували, часто з використанням resize observers і важкого JS, і вони були крихкими під реальним навантаженням.
  4. Сучасні контейнерні запити спершу з’явилися в браузерах на базі Chromium, а потім в інших основних рушіях, після великої роботи над специфікацією containment.
  5. Специфікація пов’язує запити з «query containers», які потрібно явно встановлювати через container-type (або шортхенд container).
  6. Одиниці контейнерних запитів (cqw, cqh і т. п.) відносяться до розміру query-контейнера, а не viewport, що дозволяє локально масштабуєму типографіку компонента.
  7. Containment існував до контейнерних запитів як функція продуктивності; контейнерні запити зробили containment повсякденною практикою.
  8. Дизайн-системи прискорили прийняття, бо одна «картка» може з’являтися в списку, сітці, каруселі або боковій панелі — кожна з різною шириною.

Одна цитата, щоб залишатись реалістом, з погляду надійності. Браян Керніган сказав: «Налагодження вдвічі важче, ніж написання коду.»
Якщо ви не впевнені, чи це точне формулювання, сприймайте це як парафразовану ідею і поводьтеся відповідно: зробіть вашу логіку контейнерних запитів відлагоджуваною.

Ментальна модель: containment, query containers і область дії

Контейнерні запити — це не «менші медіа-запити». Вони вводять нову залежність:
стилі елемента можуть залежати від розміру предка. Це звучить просто, допоки ви не згадаєте,
що розмір може залежати від стилів. CSS-рушії не люблять парадокси.

Ключова концепція 1: має існувати «query container»

Контейнерний запит працює лише якщо існує предок з налаштованим container-type.
Без нього ваші правила @container ніколи не співпадуть, і ви витратите півдня,
звинувачуючи неправильний файл.

Ключова концепція 2: containment запобігає циклам

Коли ви встановлюєте container-type: inline-size, ви кажете браузеру:
«Ви можете використовувати мою inline-розмірність (зазвичай ширину) для запитів, і я приймаю наслідки containment.»
Тоді браузер може обчислювати розміри в порядку, що не створює циклів.

Якщо ви неакуратно запитуєте й ширину, й висоту, або дозволяєте розмітці залежати від розміру контенту по колу,
ви можете створити нестабільні макети. Здебільшого ви уникаєте найгіршого, запитуючи лише inline-size.
Це правило 80/20.

Ключова концепція 3: область дії локальна, імена допомагають

Контейнери можна іменувати, і робити це варто, коли на сторінці є кілька можливих предків.
Інакше ви випадково виберете найближчий контейнер і здивуєтесь, чому ваш компонент змінює розклад,
коли хтось обгортає його новим div.

Жарт №1: Контейнерні запити як організаційні схеми — все зрозуміло, поки хтось не додає ще один шар «обгортки», і ніхто не знає, хто кому підпорядковується.

Базовий синтаксис, який ви дійсно використовуватимете

Визначити query-контейнер

Використовуйте скорочення, коли можете; це документує намір.
Найпоширеніший патерн — зробити обгортку компонента query-контейнером.

cr0x@server:~$ cat container-queries.css
/* Query container */
.widget {
  container-type: inline-size;
  container-name: widget;
}

/* Query by container width */
@container widget (min-width: 480px) {
  .widget .title { font-size: 1.25rem; }
  .widget .meta  { display: block; }
}

@container widget (max-width: 479px) {
  .widget .meta  { display: none; }
}

Що важливо: container-type: inline-size — це типовий робочий варіант.
Не починайте зі size, якщо у вас немає сильної причини також запитувати висоту.

Іменовані проти неіменованих контейнерів

Неіменовані контейнери працюють, але вони можуть стати пасткою в бібліотеці компонентів, де обгортки з’являються й зникають.
Використовуйте імена для міжкомандної ясності.

Одиниці контейнерних запитів (сімейство «cqw»)

Одиниці контейнерних запитів відносяться до розміру контейнера:
1cqw — це 1% ширини query-контейнера,
1cqh — 1% його висоти тощо.
Вони дозволяють масштабувати типографіку й відступи в межах доступного простору компонента.

Практична порада: використовуйте одиниці контейнера для тонкого масштабування (наприклад, padding, gap, незначна типографіка).
Не будуйте повну систему плавної типографіки всередині кожного компонента. Там криється хаос.

Патерни, орієнтовані на компонент, які витримають реальні додатки

Патерн 1: «Картка переходить зі стека у дві колонки»

Це канонічна перемога контейнерних запитів: картка змінює розклад залежно від ширини свого контейнера,
а не viewport. Це працює в сітках, бокових панелях, модалках і розділених панелях.

cr0x@server:~$ cat card.css
.card {
  container-type: inline-size;
  container-name: card;
  display: grid;
  gap: 12px;
}

.card__media { aspect-ratio: 16 / 9; background: #ddd; }
.card__body  { display: grid; gap: 8px; }

@container card (min-width: 520px) {
  .card {
    grid-template-columns: 220px 1fr;
    align-items: start;
  }
  .card__media { aspect-ratio: 1 / 1; }
}

Цей патерн зменшує залежність від сторінкових брейкпоінтів. Картка обирає свій макет
на основі доступного простору. Сторінка обирає колонки. Кожен залишається у своїй області відповідальності.

Патерн 2: «Таблиця стає списком визначень у вузьких контейнерах»

Таблиці не дуже «відповідають». У вузьких контейнерах часто краще змінити подання, ніж
стискати колонки до нечитаємих смужок. Контейнерні запити дозволяють це рішення зробити локальним.

На практиці ви зберігаєте семантичний HTML наскільки можливо. Ви все ще можете робити зміни презентації через CSS:
приховувати заголовки, відображати рядки як блоки, додавати мітки через data-label.
Важливо уникати евристик, прив’язаних до viewport. Таблиця в бічній панелі не повинна вдавати, що вона на десктопі.

Патерн 3: «Панель інструментів згортає дії в меню переповнення»

Якщо ви коли-небудь випускали панель інструментів, яка згортається невчасно, ви знаєте, чому контейнерні запити важливі.
Панель має реагувати на власну ширину, а не на те, що робить вікно загалом.

Для самої поведінки переповнення, ймовірно, знадобиться JS (вимірювання дочірніх елементів і переміщення їх).
Контейнерні запити все одно дають стабільні пороги для переходу з «показувати підписи»
на «тільки іконки» і «меню переповнення».

Патерн 4: «Компонент адаптується всередині змінюваної розділеної панелі»

Змінювані панелі — це місце, де сторінкові брейкпоінти перестають працювати.
З контейнерними запитами компонент реагує безперервно під час перетягування користувачем.
Це справжній UX, а не відповідність списку завдань.

Патерн 5: «Вкладені контейнери: стани компонентів розташовані шарами»

Вкладення контейнерів допустиме, але це також шлях до несподіваної поведінки, коли нова обгортка стає найближчим контейнером.
Використовуйте імена контейнерів і запитуйте правильний. Якщо макет вашого компонента залежить від конкретної обгортки, іменуйте її і цільтеся в неї.

Дизайн-системи: як уникнути «супу з брейкпоінтів»

Контейнерні запити — це паливо. Ви можете заправити гоночний болід або спалити гараж.
У дизайн-системах це має значення: десятки компонентів, сотні композицій, кілька команд
і релізний потік, який не зупиняється, бо CSS «майже правильний».

Виберіть «класи розмірів» компонентів, які можна описати словами

У дизайн-системі не визначайте брейкпоінти як випадкові піксельні значення для кожного компонента.
Визначте невелику кількість семантичних порогів для компонента: компактний, звичайний, просторий.
Зіставте їх із шириною контейнера. Документуйте поведінку для кожного стану.

Якщо ви не можете описати брейкпоінт без згадки пристрою, ймовірно, ви мислите шириною viewport.
Контейнерні запити — це мислення компонентами. Дотримуйтесь послідовності.

Зробіть контейнер явним у контракті компонента

Визначте, який елемент є query-контейнером. Покладіть це в API компонента і дотримуйтеся.
Якщо компонент може використовуватися самостійно, він має створювати свій контейнер.
Якщо він має успадковувати контейнер, документуйте це — і очікуйте, що це порушать о 2-й ночі.

Віддавайте перевагу менше запитів і сильнішим примітивам макету

Використовуйте grid/flex і внутрішні розміри першочергово. Потім застосовуйте контейнерні запити для кількох важливих змін форми.
Якщо кожні 40px щось змінюється, ви створили тест для терпіння людей.

Стратегія тестування: знімки макетів на ширинах компонентів, а не viewport

Ваші тести повинні рендерити компонент на репрезентативних ширинах контейнера:
320, 420, 520, 720 тощо. Ці числа — приклади; оберіть те, що відповідає вашим композиціям.
Суть: поведінку компонента має бути можна протестувати без повного обгортувачa сторінки.

Відлагодження в продакшені: що ламається і чому

Режими відмов контейнерних запитів — здебільшого експлуатаційні: функція проста,
але ваш DOM — ні. Контейнери обгортають компоненти. Стилі рефакторяться. Новий компонент макету додає
contain або змінює display. Раптом ваш компонент «застрягає» в компактному режимі.

Найпоширеніша корінна причина: немає query-контейнера

Якщо container-type не встановлено (або встановлено на неправильному предку), нічого не співпаде.
CSS парситься нормально. Консолі помилок немає. Просто… не працює.
Ось чому вам потрібен рутін діагностики, а не відчуття.

Друга поширена причина: запит до неправильного контейнера

Неіменовані контейнери обирають найближчий предок. Якщо обгортка макету стала контейнером з якоїсь причини,
ваш компонент може почати орієнтуватись на ту обгортку замість призначеної.
Імена цьому запобігають. Використовуйте їх.

Третя причина: зв’язування стилів викликає ривок в розкладці

Контейнерні запити можуть спричиняти «гойдання», якщо запит змінює розклад настільки, що змінює розмір контейнера,
що змінює результат запиту, що знову змінює розклад. Браузери намагаються уникати циклів, але ви все одно можете
створити ривкі переходи або несподівані перефлоування.

Жарт №2: Якщо ваш компонент коливається між двома макетами, вітаємо — ви винайшли CSS-метроном. Користувачі не просили джазову метрономію.

Перевірка реальності продуктивності

Контейнерні запити реалізовані в рушіях, тож ви уникаєте старого кошмару з JS-поліміфом.
Але «нативне» не означає «безкоштовне». Якщо ви встановите контейнери скрізь і додасте багато
детальних запитів, це може створити вимірювані витрати на перерахунок стилів під час зміни розміру і динамічних змін макету.

Виправлення нудне: менше контейнерів, менше запитів, краща композиція і профілювання з реальними взаємодіями.
Це та сама дисципліна, що й на бекенді: вимірюйте, обмежуйте і спрощуйте.

Швидкий план діагностики

Коли компонент, що базується на контейнерних запитах, поводиться неправильно, ви хочете швидко знайти вузьке місце.
Ось виробничо-дружній порядок тріяжу, що допомагає уникнути зайвих глибинних перевірок.

1) Перевірте, чи існує query-контейнер і який саме використовується

  • Інспектуйте елемент у DevTools.
  • Знайдіть найближчого предка з container-type (або шортхенд container).
  • Якщо їх кілька, підтвердіть, що ви мали на увазі найближчий — або використайте container-name, щоб цілитись у потрібний.

2) Перевірте фактичну inline-ширину контейнера під час виконання

  • У DevTools перевірте обчислену ширину розмітки контейнера.
  • Порівняйте її з порогами запитів.
  • Якщо вона близька до порога і мерехтить, ймовірно, у вас петля зворотного зв’язку або проблема округлення.

3) Підтвердіть, що умова запиту співпадає з вашими очікуваннями

  • Перевірте, чи ви правильно використовуєте min-width/max-width всередині @container.
  • Переконайтесь, що ваші контейнерні запити таргетують іменований контейнер, якщо ви використовуєте імена.
  • Перевірте, чи випадково не використали одиниці контейнера (cqw), очікуючи одиниць viewport (vw).

4) Шукайте цикли розкладки та побічні ефекти containment

  • Якщо запит змінює padding/border/scrollbar, це може змінити розмір контейнера.
  • Якщо ви запитуєте висоту, будьте особливо обережні: контент змінює висоту; висота тригерить запит; запит змінює контент.
  • Віддавайте перевагу inline-size, якщо вам справді не потрібні висотні запити.

5) Профілюйте перед «оптимізацією»

  • Якщо додаток пригальмовує при зміні розміру або відкритті панелей, профілюйте перерахунок стилів і layout.
  • Видаліть непотрібні контейнери і спростіть пороги запитів.

Поширені помилки: симптом → корінна причина → виправлення

1) Симптом: правила @container ніколи не застосовуються

Корінна причина: Жоден предок не має встановленого container-type, отже немає query-контейнера.

Виправлення: Додайте container-type: inline-size до обгортки компонента (або до конкретної обгортки макету) і повторно перевірте обчислені стилі.

2) Симптом: компонент виглядає правильно на одній сторінці, але неправильно в модалці/біксайд

Корінна причина: Ви використали медіа-запити для компонента, що вбудовується в різні ширини контейнерів.

Виправлення: Перенесіть логіку брейкпоінтів у контейнерні запити на рівні компонента. Залиште viewport-запити лише для фреймінгу сторінки.

3) Симптом: компонент несподівано змінює макет після рефакторингу

Корінна причина: Нова обгортка стала найближчим контейнером (вибір неіменованого контейнера), змінивши, до якого контейнера ведеться запит.

Виправлення: Іменуйте призначений контейнер (container-name) і явно цільтесь на нього в @container.

4) Симптом: макет коливається біля брейкпоінта

Корінна причина: Запит змінює те, що змінює ширину контейнера (scrollbar, padding, колонки сітки), викликаючи треш порогів.

Виправлення: Додайте гістерезис (використайте трохи рознесені min/max пороги), уникайте змін, що впливають на ширину на порозі, або реструктуруйте так, щоб ширина контейнера була стабільною.

5) Симптом: типографіка дивно масштабується у вкладених компонентах

Корінна причина: Одиниці контейнерних запитів відносяться до найближчого query-контейнера; вкладення змінює довідку несподівано.

Виправлення: Використовуйте іменовані контейнери для контекстів масштабування типографіки або віддавайте перевагу rem-типографіці з дискретними контейнерними кроками.

6) Симптом: пригальмовування при зміні розмірів панелей або відкритті висувних меню

Корінна причина: Занадто багато контейнерів і занадто багато порогів викликають часті перерахунки стилів/макета під час динамічного зміни розміру.

Виправлення: Зменште область застосування контейнерів, скоротіть пороги і уникайте контейнерних запитів для мікрозмін. Виміряйте за допомогою інструментів профілювання до/після.

7) Симптом: запит працює в одному браузері, але не в іншому

Корінна причина: Часткова підтримка, старі вбудовані вебв’ю або налаштування збірки, що видаляє/переписує невідомі at-правила.

Виправлення: Додайте прогресивне покращення: базовий макет без контейнерних запитів, а потім шар контейнерних запитів. Перевірте, що ваш CSS-процес зберігає @container.

Три корпоративні міні-історії з практики

Міні-історія 1: Інцидент через неправильне припущення

Команда випустила нову «InsightCard», що використовувалась у всьому аналітичному продукті: дашборди, бічні панелі та повноекранний звіт.
Початкова реалізація використовувала viewport-медіа-запити. Вона виглядала нормально у повноекранному звіті, бо так і мало бути.

Потім один клієнт-ентерпрайз побудував кастомний дашборд: триколонкова сітка з висувною правою панеллю.
Коли панель розгорнулась, картки всередині неї й далі рендерилися в «десктопному режимі», бо breakpoint по viewport був задоволений.
Всередині 320px-широкої панелі картка намагалася показати двоколонну внутрішню сітку, довгі підписи і легенду графіка.
Результат — переповнення, обрізаний текст і пастка прокрутки всередині контейнера прокрутки. Підтримка назвала це «непридатним». Вони були праві.

Неправильне припущення було простим: ширина viewport апроксимує ширину компонента. Ні.
У сучасних UI є розділені панелі, липкі бокові панелі і вбудовані модулі. Viewport — це брехня, і вона тільки зростала.

Виправлення не було героїчним. Вони визначили обгортку картки як іменований query-контейнер і перенесли внутрішні брейкпоінти картки в @container.
Медіа-запити залишилися лише для сторінкових змін сітки. Після випуску та сама картка поводилась правильно і в панелі, і в модалці, і у повноекранному звіті.

Міні-історія 2: Оптимізація, що призвела до зворотного ефекту

Інша організація загорілася ідеєю «уніфікувати адаптивність», зробивши кожну обгортку макету query-контейнером.
Хедери, секції, клітинки сітки, панелі — все мало container-type: inline-size.
Це презентували як майбутнє, але це також був чудовий спосіб зробити відлагодження неможливим.

Перша видима проблема була дивною: компоненти почали реагувати на різні контейнери залежно від того, де їх відрендерено.
Компонент, що очікував запитувати свій власний обгорт, тепер опитував зовнішню обгортку, бо DOM змінився.
Друга проблема була менш видимою, але дорожчою: витрати на перерахунок стилів зросли під час перетягування з зміною розміру.
UI не «впав», він просто став відчутно повільним.

«Оптимізація» команди створила величезний неявний граф залежностей. Забагато контейнерів означає багато можливих контекстів запитів.
І оскільки багато контейнерів були надлишкові, браузер виконував зайву роботу. У дашбордах продуктивності нічого не кричало.
Користувачі просто перестали довіряти UI.

Стратегія відкоту була повчальною: вони прибрали container-type з загальних обгорток і залишили його лише на коренях компонентів, які дійсно потребували.
Вони іменували ключові контейнери. Відлагодження стало зрозумілішим, продуктивність покращилась, і система знову стала пояснюваною — саме те, що потрібно в продакшені.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Платформена команда підтримувала бібліотеку компонентів для кількох продуктів.
Вони запровадили контейнерні запити обережно: кожен компонент мав базовий макет без контейнерних запитів,
а потім контейнерні запити додавались як поступове покращення. Також вони написали маленьку внутрішню настанову:
«Брейкпоінти компонентів мають документуватись як стани компонентів, а контейнери мають бути іменовані.»

Через місяць одна команда вбудувала бібліотеку в середовище третьої сторони з старішим webview.
Контейнерні запити не застосовувалися. Команда чекала катастрофи, бо сучасні CSS-фічі зазвичай провалюються голосно в застарілих контекстах.
Натомість UI залишився придатним: базовий макет був простим, гнучким і не залежав від підтримки запитів.

Реакція на інцидент була «нудною» в найкращому сенсі. Вони виявили відсутність підтримки в QA,
прийняли базовий досвід для того хоста і уникнули впровадження крихкого поліміфу.
Продукт продовжив працювати. Реліз залишився за графіком. Ніхто не сидів всю ніч, борючись з CSS.

Це той тип «інженерного успіху», що не стає легендою, бо не створює драму.
Він просто її запобігає.

Практичні завдання з командами (і рішеннями)

Контейнерні запити — це CSS, але їх впровадження — це операційна робота: конвеєри збірки, підтримка браузерів,
регресійне тестування і профілювання продуктивності. Нижче реальні завдання, які можна виконати в типовому репозиторії.
Кожне містить команду, приклад виводу, що це означає, і рішення.

Завдання 1: Перевірити браузерлисти (щоб знати, що ви обіцяєте)

cr0x@server:~$ cat package.json | sed -n '1,120p'
{
  "name": "ui-app",
  "version": "1.0.0",
  "browserslist": [
    "last 2 chrome versions",
    "last 2 firefox versions",
    "last 2 safari versions",
    "not dead"
  ]
}

Що означає вивід: Ваші інструменти трансляції та CSS-інструменти будуть таргетувати ці браузери.

Рішення: Якщо потрібно підтримувати старі вбудовані webview, плануйте базові макети і не покладайтеся на контейнерні запити для критичної юзабіліті.

Завдання 2: Переконатися, що збірка CSS не видаляє правила @container

cr0x@server:~$ rg -n "@container" src/styles
src/styles/card.css:11:@container card (min-width: 520px) {
src/styles/widgets.css:21:@container widget (max-width: 479px) {

Що означає вивід: Ваше джерело містить контейнерні запити там, де очікується.

Рішення: Якщо в кінцевому бандлі їх немає, перевірте конфігурацію PostCSS/мінімізатора і переконайтесь, що вони зберігають невідомі at-правила.

Завдання 3: Перевірити скомпільований CSS на наявність контейнерних запитів

cr0x@server:~$ ls -lh dist/assets/app.css
-rw-r--r-- 1 cr0x cr0x 412K Sep  4 11:20 dist/assets/app.css
cr0x@server:~$ rg -n "@container" dist/assets/app.css | head
1842:@container card (min-width: 520px){.card{grid-template-columns:220px 1fr}}
1901:@container widget (max-width: 479px){.widget .meta{display:none}}

Що означає вивід: Правила контейнерних запитів пережили ваш pipeline.

Рішення: Якщо їх немає, виправте збірку перед відлагодженням макету; інакше ви відлагоджуєте код, який ніколи не потрапляє в реліз.

Завдання 4: Перевірити синтаксичну коректність CSS (швидкий lint)

cr0x@server:~$ npx stylelint "src/**/*.css"
src/styles/card.css
  11:1  warning  Unexpected unknown at-rule "@container"  at-rule-no-unknown

Що означає вивід: Ваш лінтер не розпізнає контейнерні запити (або не налаштований для них).

Рішення: Оновіть конфіг stylelint, щоб дозволити @container. Інакше розробники можуть «виправити» вашу фічу, видаливши її.

Завдання 5: Перевірити конфіг stylelint щодо обробки at-правил

cr0x@server:~$ cat .stylelintrc.json
{
  "rules": {
    "at-rule-no-unknown": [true, {
      "ignoreAtRules": ["container", "layer"]
    }]
  }
}

Що означає вивід: Лінтер перестане позначати валідний синтаксис контейнерних запитів як помилку.

Рішення: Запровадьте це у всіх репозиторіях перед широким використанням контейнерних запитів.

Завдання 6: Відстежити, де ви встановлюєте container-type (уникнути «контейнерів скрізь»)

cr0x@server:~$ rg -n "container-type:" src | head -n 20
src/styles/card.css:2:  container-type: inline-size;
src/styles/widget.css:3:  container-type: inline-size;
src/styles/layout.css:5:  container-type: inline-size;

Що означає вивід: Ви бачите, які файли вводять query-контейнери.

Рішення: Якщо загальні обгортки за замовчуванням є контейнерами, перегляньте рішення. Робіть контейнери навмисними й іменованими.

Завдання 7: Виявити неіменовані контейнери (важче для відлагодження)

cr0x@server:~$ rg -n "container-name:" src/styles | wc -l
2

Що означає вивід: У вас мало іменованих контейнерів відносно використання контейнерів.

Рішення: Додайте імена для контейнерів, що є частиною контракту компонента або з’являються у кількох вкладених контекстах.

Завдання 8: Знайти пороги контейнерних запитів у кодовій базі (стандартизувати їх)

cr0x@server:~$ rg -n "@container .*min-width" src/styles | head -n 20
src/styles/card.css:11:@container card (min-width: 520px) {
src/styles/widget.css:10:@container widget (min-width: 480px) {
src/styles/table.css:27:@container results (min-width: 640px) {

Що означає вивід: Брейкпоінти існують на рівні компонентів.

Рішення: Якщо значення довільні, створіть настанови станів компонентів (компактний/звичайний/просторий) і уніфікуйте пороги там, де це має сенс.

Завдання 9: Переконатися, що базовий макет працює без контейнерних запитів (режим feature-flag)

cr0x@server:~$ rg -n "@container" src/styles/card.css
11:@container card (min-width: 520px) {
cr0x@server:~$ sed -n '1,40p' src/styles/card.css
.card {
  container-type: inline-size;
  container-name: card;
  display: grid;
  gap: 12px;
}

Що означає вивід: Базовий .card уже має прийнятний макет без правила запиту.

Рішення: Тримайте базовий макет придатним; розглядайте контейнерні запити як покращення, а не як передумову для читабельності.

Завдання 10: Використати Playwright для рендерингу компонента на контрольованих ширинах контейнера

cr0x@server:~$ cat tests/card-container-width.spec.js
const { test, expect } = require('@playwright/test');

test('card switches layout at container width', async ({ page }) => {
  await page.setContent(`
    
    
media
body
`); const card = page.locator('.card'); await expect(card).toHaveCSS('grid-template-columns', 'none'); });
cr0x@server:~$ npx playwright test tests/card-container-width.spec.js
Running 1 test using 1 worker

  ✓  1 tests/card-container-width.spec.js:3:1 › card switches layout at container width (1.2s)

  1 passed (2.0s)

Що означає вивід: Ви можете тестувати адаптацію макету, керовану контейнером, детерміністично.

Рішення: Додайте тести для ключових меж станів, щоб майбутні зміни DOM не змінювали тихо, який контейнер опитується.

Завдання 11: Аудит бандлу на дублікати правил контейнерних запитів (роздування CSS)

cr0x@server:~$ rg -n "@container card" dist/assets/app.css | wc -l
18

Що означає вивід: Є багато блоків container query для card — можливо дублікати від варіантів компонентів.

Рішення: Консолідуйте спільні правила контейнерних запитів або рефакторьте варіанти так, щоб вони складалися, а не дублювались.

Завдання 12: Перевірити випадкове використання одиниць контейнера там, де очікували одиниці viewport

cr0x@server:~$ rg -n "\bcq(w|h|i|b|min|max)\b" src/styles | head -n 30
src/styles/hero.css:14:  font-size: clamp(1.2rem, 2.2cqw, 2.0rem);
src/styles/badge.css:8:  padding: 0.6cqi 1.2cqi;

Що означає вивід: Одиниці контейнера використовуються в типографіці/відступах.

Рішення: Підтвердіть, що кожне використання має правильний контекст контейнера і не буде несподівано масштабуватись при вкладенні. Якщо сумніваєтесь, віддавайте перевагу rem + дискретним контейнерним крокам.

Завдання 13: Переконатися, що ніхто «допоміжно» не додав container-type до глобальних обгорток

cr0x@server:~$ rg -n "container-type: inline-size" src/styles/layout.css
5:.layout-shell { container-type: inline-size; }

Що означає вивід: Високорівнева обгортка є query-контейнером для всього всередині неї.

Рішення: Якщо немає документованої причини, приберіть це. Глобальні контейнери створюють неявну зв’язність і заплутаний вибір контейнера.

Завдання 14: Створити легкий «звіт контрактів контейнера» для PR-рев’ю

cr0x@server:~$ rg -n "container-(type|name):" src/styles | sort
src/styles/card.css:2:  container-type: inline-size;
src/styles/card.css:3:  container-name: card;
src/styles/table.css:4:  container-type: inline-size;
src/styles/table.css:5:  container-name: results;
src/styles/widget.css:3:  container-type: inline-size;
src/styles/widget.css:4:  container-name: widget;

Що означає вивід: Стисла інвентаризація визначень контейнерів.

Рішення: Вимагайте імен для спільних компонентів. Змушуйте рев’юверів питати: «Чи це правильний контейнер і чи він стабільний при рефакторах?»

Завдання 15: Перевірити, що ваш мінімізатор CSS не переписує контейнерні запити неправильно

cr0x@server:~$ node -p "require('fs').readFileSync('dist/assets/app.css','utf8').includes('@container')"
true

Що означає вивід: Базова логічна перевірка, що вихід містить at-правило.

Рішення: Якщо false, зупиніться. Виправте pipeline. Не випускайте фічу, яку ваша збірка видаляє.

Контрольні списки / покроковий план

Покроковий план впровадження контейнерних запитів у реальній кодовій базі

  1. Виберіть один компонент з високою цінністю (картка, панель інструментів, підсумок даних, блок цін).
    Оберіть те, що повторно використовується в макетах і наразі має проблеми з брейкпоінтами.
  2. Визначте контейнер компонента (зазвичай корінь компонента).
    Додайте container-type: inline-size і container-name.
  3. Спочатку напишіть базовий макет, який працює без контейнерних запитів.
    Зробіть його читабельним, але не ідеальним.
  4. Додайте 1–3 пороги контейнерних запитів, які відповідають станам компонента.
    Тримайте переходи значущими (stack → columns, hide → show, compact → spacious).
  5. Забороніть неіменовані контейнери в спільних компонентах.
    Імена запобігають випадковим змінам поведінки при додаванні обгорток.
  6. Додайте тести ширини компонента (візуальні або перевірки CSS).
    Тестуйте на ширинах навколо ваших порогів.
  7. Інвентаризуйте контейнери у міру поширення.
    Забагато контейнерів — ризик підтримки і продуктивності.
  8. Впроваджуйте поступово.
    Слідкуйте за шаблонами регресій: вкладені макети, модалки, бокові панелі та вбудовані контексти.
  9. Профілюйте динамічні взаємодії (перетягування для зміни розміру, відкриття/закриття висувних панелей, нескінченна прокрутка).
    Якщо продуктивність погіршується, зменшіть гранулярність запитів і область контейнера.
  10. Задокументуйте контракт для кожного компонента:
    який елемент є контейнером, які стани існують і які ширини їх тригерять.

Операційний чекліст для PR-рев’ю

  • Чи має компонент іменований query-контейнер?
  • Чи виражені брейкпоінти як стани компонентів, а не як назви пристроїв?
  • Чи прийнятний базовий макет, коли контейнерні запити не застосовуються?
  • Чи стабільні пороги запитів (немає коливань на межах)?
  • Чи додає зміна container-type до загальної обгортки? Якщо так — навіщо?
  • Чи використовуються одиниці контейнера навмисно і з відомим контекстом?
  • Чи є хоча б один тест, що зачіпає межі станів?

FAQ

1) Чи повинні контейнерні запити повністю замінити медіа-запити?

Ні. Використовуйте медіа-запити для рішень рівня сторінки, прив’язаних до viewport (глобальна навігація, загальна сітка).
Використовуйте контейнерні запити для внутрішнього макету компонентів. Змішування без плану призведе до «whiplash» брейкпоінтів.

2) Що мінімально потрібно, щоб @container запрацював?

Предок-елемент з встановленим container-type, зазвичай inline-size.
Без цього немає query-контейнера і ваші правила не співпадуть.

3) Чому мої контейнерні запити працюють в одному місці, але не в іншому?

Ймовірно, ви опитуєте інший контейнер, ніж думаєте (вибір найближчого предка).
Іменуйте контейнери й цільтесь у них явно, щоб уникнути випадкових змін при додаванні обгорток.

4) Чи безпечні контейнерні запити для продуктивності?

Загалом — так, якщо використовувати навмисно, але вони не без витрат.
Багато контейнерів плюс багато порогів можуть збільшувати роботу з перерахунком стилів/макета під час динамічних змін.
Профілюйте важливі взаємодії і тримайте запити грубими.

5) Чи варто використовувати container-type: size?

Лише якщо вам справді потрібно запитувати і ширину, і висоту. Більшість компонентів повинні запитувати лише ширину.
Висотні запити частіше створюють петлі зворотного зв’язку і ривки, бо висоту зазвичай визначає контент.

6) Чим одиниці контейнера (cqw, cqh) відрізняються від vw/vh?

Одиниці контейнера відносяться до розміру query-контейнера, а не до viewport.
Це круто для локального масштабування, але заплутано у вкладених контекстах. Використовуйте їх для дрібних налаштувань; тримайте основну типографіку стабільною.

7) Який найкращий спосіб тестувати поведінку контейнерних запитів?

Рендерте компонент на контрольованих ширинах контейнера в автоматизованих тестах.
Не покладайтеся лише на повноекранні скриншоти. Тести ширини компонентів ловлять регресії в вбудуванні.

8) Як поводитися зі старими браузерами або вбудованими webview?

Прогресивне покращення. Базовий макет має працювати без контейнерних запитів; контейнерні запити покращують досвід, коли підтримка є.
Уникайте крихких поліміфів, якщо лише у вас немає ресурсів на їх підтримку.

9) Чи можна вкладати контейнерні запити?

Так, але треба явно вказувати, який контейнер ви опитуєте.
Використовуйте container-name, щоб уникнути сюрпризів. Вкладення без імен — це шлях до того, що адаптивна поведінка стає фольклором.

10) Який найбільший «підводний камінь», на який натрапляють команди?

Сприйняття контейнерних запитів як магічної заміни мислення про макет.
Якщо ваш компонент структурно крихкий, контейнерні запити просто дозволять йому ламатися в ще біль
їй кількості місць, і цікавіше.

Висновок: наступні кроки, які можна випустити

Контейнерні запити — це правильна абстракція для сучасної композиції UI: компоненти реагують на простір, який їм дають,
а не на екран, який ви тримаєте. Використані грамотно, вони усувають цілий клас адаптивних багів.
Використані необережно, вони створюють нові, які важче помітити і простіше спровокувати.

Практичні наступні кроки:

  • Виберіть один високоприоритетний компонент і перенесіть його внутрішню адаптивність з медіа-запитів на іменований контейнер.
  • Тримайте базовий макет придатним без контейнерних запитів. Це ваша історія сумісності та стійкості.
  • Уніфікуйте стани компонентів і пороги, і напишіть тести на ширини контейнерів навколо меж.
  • Аудитуйте й скоротіть «фонові» контейнери в обгортках макету. Контейнери мають бути навмисними, а не заразними.
  • Коли щось ламається, користуйтесь планом швидкої діагностики; не вгадуйте, який предок є контейнером.

Мета не в тому, щоб використовувати найновішу CSS-фічу скрізь. Мета — припинити випускати UI, який працює лише в одному макеті, який ви випадково протестували.
Контейнерні запити, використані з обмеженням, допоможуть вам цього досягти.

← Попередня
Proxmox став повільнішим після оновлення: перші перевірки, які зазвичай виявляють причину
Наступна →
SMB/CIFS у Proxmox повільний для дисків VM: чому це погано і що використовувати натомість

Залишити коментар