Действуй как senior CSS-инженер. Переведи {{component}} с viewport-брейкпойнтов на container queries. Baseline: {{browser_baseline}}.
1. Когда @container побеждает @media
Container queries нужны, если компонент не знает, в какой колонке он окажется:
- Card в галерее — может лежать в 1, 2, 3 или 4 колонках. На 1280px viewport карточка может быть и 1200px, и 280px шириной.
- Sidebar widget — один и тот же компонент в правой колонке (320px) и в полноширинном модальном окне (900px).
- Embed / iframe виджет — встроен на чужой сайт, viewport ≠ ширина виджета.
- CMS-блок — редактор выбирает 1/2/3 колонки в визуальном конструкторе.
- Dashboard tile — пользователь сам растягивает плитку drag-and-drop.
Где @media всё ещё уместен: глобальный layout страницы (header, footer, основная сетка), navigation patterns (hamburger vs полное меню), общая типографика body.
2. Базовая разметка
/* Объявляем контейнер */
.card-shell {
container-type: inline-size;
container-name: card;
}
/* Стилизуем содержимое в зависимости от ширины контейнера */
@container card (min-width: 480px) {
.card { display: grid; grid-template-columns: 160px 1fr; gap: 1.5rem; }
}
@container card (min-width: 720px) {
.card { grid-template-columns: 240px 1fr 200px; }
}
Важно:
container-type: inline-size— отслеживаем только ширину (дёшево).size— и ширину, и высоту (дороже, нужно только для height-based logic).container-nameобязателен, если на странице несколько вложенных контейнеров.- Свойства самого контейнера (
width,padding) нельзя менять внутри его же@container— это вызовет infinite loop. Меняй только потомков.
3. Naming convention
Имя контейнера — это контракт. Назови по роли, не по размеру:
container-name: card; /* role */
container-name: sidebar; /* role */
container-name: product-tile; /* role */
/* НЕ так */
container-name: small; /* размер — будет врать */
container-name: c1; /* непонятно */
Префиксы для команд: ui-card, shop-product-tile, dash-widget — чтобы не было коллизий между фичами.
4. Fallback стратегия
Для Safari ≤ 15 и старого Chrome нужен fallback. Три подхода:
A. Прогрессивное улучшение через @supports
.card { /* mobile-first viewport-based стили */ }
@media (min-width: 768px) { .card { /* tablet */ } }
@supports (container-type: inline-size) {
.card-shell { container-type: inline-size; container-name: card; }
.card { /* сбрасываем viewport-based, переключаемся на @container */ }
@container card (min-width: 480px) { .card { /* ... */ } }
}
B. Polyfill (container-query-polyfill) — для критичных публичных страниц, ~6KB gzip, наблюдает за resize контейнеров через ResizeObserver. Не использовать на 1000+ контейнерах на странице.
C. JS-based опциональный класс
const ro = new ResizeObserver(([entry]) => {
const w = entry.contentRect.width;
entry.target.dataset.size = w < 480 ? 'sm' : w < 720 ? 'md' : 'lg';
});
ro.observe(cardShell);
Селекторы вида [data-size="lg"] .card работают везде.
5. Container query units
cqw, cqh, cqi, cqb, cqmin, cqmax — относительные к контейнеру, не к viewport. Идеально для fluid spacing внутри карточки:
@container card (min-width: 400px) {
.card__title { font-size: clamp(1rem, 4cqi, 1.5rem); }
.card__padding { padding: 4cqi; }
}
6. Формат вывода
## Refactor: viewport → container
### Before (@media)
[фрагмент кода]
### After (@container)
[фрагмент кода]
### Контейнерная карта
| Container | Type | Breakpoints | Где живёт |
|---|---|---|---|
| card | inline-size | 480, 720 | gallery, sidebar, modal |
### Fallback
[стратегия для {{browser_baseline}}]
Anti-patterns
- ❌
container-type: sizeбез причины — браузер пересчитывает и width, и height на каждый resize, дороже в 2-3 раза. - ❌ Изменение
widthконтейнера внутри его же@container— infinite loop, страница виснет. - ❌ Container query без
container-nameпри вложенных контейнерах — стиль уходит в ближайшего родителя, не туда, куда ждёшь. - ❌ Полная замена @media на @container в одну ночь — не трогай глобальный layout, header, нав. Это viewport-territory.
- ❌ Имя контейнера по размеру (
small,large) — через месяц компонент шире, имя врёт, рефакторить страшно. - ❌ Polyfill на админке с 500+ карточками — ResizeObserver на каждый узел убивает FPS.
- ❌ Отказ от mobile-first — забыли базовые стили без @container, при отсутствии поддержки страница без layout.
- ❌ Container queries для глобальной типографики body — это работа
clamp()и viewport units, а не контейнеров.
Mobile-friendly аудит (375px)
Аудит мобильной версии на iPhone SE: тач-таргеты, скролл, попапы, tap-vs-hover, input zoom.
Дизайн-токены из референса
Извлечь систему цветов, типографики, теней, радиусов и spacing из примера и оформить как токены.
Стратегия брейкпойнтов
Не «3 устройства», а содержательные точки. Где менять layout и зачем.