Вложенность, @layer и контейнерные запросы
Урок про три современных механизма организации стилей: нативную вложенность CSS, каскадные слои @layer и контейнерные запросы для компонентной адаптивности.
Нативная вложенность позволяет писать вложенные правила без препроцессора, @layer явно управляет приоритетом каскада, а container queries адаптируют компонент по размеру его контейнера, а не всего окна.
Зачем это на практике
Три вещи, ради которых раньше подключали Sass и придумывали методологии, теперь умеет сам браузер. Вложенность убирает повторение длинных префиксов. @layer решает вечную войну специфичности и порядка подключения. А контейнерные запросы наконец делают компоненты по-настоящему переиспользуемыми: карточка сама знает, как выглядеть в узкой колонке и в широкой, не привязываясь к ширине экрана.
Нативная вложенность CSS
Теперь правила можно вкладывать прямо в CSS. Вложенный селектор раскрывается относительно родительского. Символ & ссылается на сам родительский селектор.
.card {
padding: 16px;
background: var(--card, #f3f4f6);
/* = .card .title */
.title { font-weight: 600; }
/* & — это .card; = .card:hover */
&:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
/* = .card > header .title (вложенность глубже) */
& > header .title { color: #2563eb; }
/* медиа-запрос тоже можно вкладывать */
@media (min-width: 600px) {
padding: 24px;
}
}
Важная тонкость: без & вложенный селектор означает потомка. Чтобы получить «тот же элемент с модификатором», & обязателен — &.active даёт .card.active, а просто .active внутри даст .card .active (другой элемент). Это главное отличие от привычного поведения Sass, где .active в некоторых стилях по умолчанию тоже трактовался как потомок, но &-логика здесь строже.
@layer — управление каскадом
Каскадные слои дают приоритет между группами стилей, который сильнее обычной специфичности. Порядок объявления слоёв определяет их вес: чем позже объявлен слой, тем он «главнее».
/* Объявляем порядок один раз: reset слабее всех, utilities — сильнее */
@layer reset, base, components, utilities;
@layer base {
/* даже #id здесь проиграет слою выше */
a { color: #2563eb; }
}
@layer components {
.link { color: rebeccapurple; }
}
@layer utilities {
.text-muted { color: #6b7280; }
}
Революция в том, что слой бьёт специфичность: правило из utilities победит правило из base, даже если в base селектор гораздо специфичнее. Это решает классическую боль — когда служебный класс .text-muted не мог перебить какой-нибудь .article #content a. Стили вне любых слоёв считаются «важнее» всех именованных слоёв (кроме случаев с !important, где порядок переворачивается).
Слои особенно полезны при подключении чужого CSS: оборачиваете библиотеку в @layer vendor, и ваши собственные стили в более позднем слое спокойно её перекрывают без !important.
Контейнерные запросы (container queries)
Медиа-запросы смотрят на окно. Но переиспользуемому компоненту важна не ширина экрана, а ширина места, куда его положили. Контейнерные запросы отвечают именно на это.
Сначала родителя объявляют контейнером через container-type, затем дочерние стили реагируют на его размер через @container.
/* 1. Назначаем контекст: следим за шириной (inline-осью) */
.card-host {
container-type: inline-size;
container-name: card;
}
/* 2. Стили карточки зависят от ширины контейнера, не окна */
.card { display: block; }
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
gap: 16px;
}
}
Одна и та же карточка в узком сайдбаре останется вертикальной, а в широкой основной колонке станет двухколоночной — потому что условие проверяет её собственный контейнер. Компонент перестаёт зависеть от того, на какой странице он живёт. Появились и контейнерные единицы: cqw (1% ширины контейнера), cqi, cqb — например, font-size: 5cqi масштабирует текст относительно контейнера, а не окна.
Как это работает под капотом
Вложенность — это синтаксический сахар: браузер раскрывает вложенные правила в плоские эквиваленты на этапе парсинга, специфичность считается по итоговому селектору. @layer добавляет в каскад ещё один уровень сравнения перед специфичностью: сначала браузер смотрит на важность и слой, и только при равенстве слоёв переходит к специфичности и порядку. Container queries требуют от движка containment: объявляя container-type: inline-size, вы обещаете браузеру, что внутренний контент не влияет на ширину контейнера «обратно», — это разрывает потенциальный цикл «размер зависит от стилей, которые зависят от размера» и делает запросы вычислимыми.
Частые ошибки
- Забыть & для модификатора. Внутри
.cardправило.activeозначает потомка.card .active; нужный.card.activeдаёт только&.active. - Думать, что @layer — про специфичность внутри слоя. Внутри одного слоя всё решает обычная специфичность; слои сравниваются только между собой.
- Перепутать порядок слоёв. Главнее тот, что объявлен позже. Часто список
@layerзабывают объявить заранее, и порядок задаётся случайно — первым появлением. - Запрашивать контейнер, не объявив его. Без
container-typeна предке@containerне сработает. - Ставить container-type на сам адаптируемый элемент. Контейнером должен быть родитель — элемент не может запрашивать собственный размер, который сам же меняет.
Итоги
- Нативная вложенность убирает повтор префиксов;
&— ссылка на родительский селектор, без неё вложенное правило означает потомка. @layerвводит приоритет между группами стилей, который сильнее специфичности; главнее слой, объявленный позже.- Слои идеальны для укрощения чужих библиотек без
!important. - Container queries адаптируют компонент по ширине его контейнера (
container-type+@container), а не по ширине окна. - Появились контейнерные единицы
cqi/cqw/cqbдля размеров относительно контейнера.