Kodokon kodokon.com

Слои каскада (@layer) и @scope

Освой два новых критерия каскада - слои и близость области - чтобы выстраивать архитектуру стилей без войн специфичности.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

Каскад разрешает конфликтующие объявления по упорядоченным критериям: источник и важность, затем контекст, затем стили из атрибута style, затем слои (@layer), затем специфичность и, наконец, порядок появления. Ключевой момент: слои оцениваются раньше специфичности. Правило .button в слое с более высоким приоритетом побеждает #nav div.menu a.button из слоя пониже - его специфичность (1,2,2) не значит ничего против порядка слоёв. Именно это делает @layer таким мощным для архитектуры дизайн-системы: приоритет объявляется один раз и структурно, вместо того чтобы отвоёвывать его селектор за селектором.

CSS
@layer reset, base, components, utilities;

@layer components {
  .button { background: rebeccapurple; }
}

@layer base {
  button { background: gray; }
}
Объявление в начале фиксирует порядок: components побеждает base независимо от порядка блоков в файле.

Две тонкости спецификации, которые обязательно нужно знать. Первая: стили вне любого слоя побеждают слоистые стили (для обычных объявлений) - они образуют неявный слой, поставленный последним. Твои локальные переопределения поэтому всегда бьют слоистую библиотеку. Вторая: с !important порядок полностью переворачивается. Побеждают слои, объявленные первыми, а !important внутри слоя бьёт !important вне любого слоя. Объявление с !important в твоём слое reset становится почти непобедимым.

CSS
@layer base, theme;

@layer base {
  a { color: black !important; }
}

@layer theme {
  a { color: purple !important; }
}
С !important порядок переворачивается: побеждает чёрный, потому что base объявлен первым.

@scope ограничивает набор правил между корнем области и необязательной нижней границей: @scope (.card) to (.card__content) берёт элементы, которые являются потомками .card, но находятся вне .card__content - знаменитый donut scope, идеальный, чтобы стилизовать оболочку компонента, не протекая в подставляемое в неё содержимое. Внутри :scope указывает на сам корень. И @scope вводит совершенно новый критерий каскада: близость. При равной специфичности побеждает правило, чей корень области ближе к элементу - то, чего никогда не могли выразить ни BEM, ни комбинаторы.

CSS
@scope (.card) to (.card__content) {
  :scope {
    padding: 1rem;
    border: 1px solid oklch(0.85 0.02 260);
  }
  img { border-radius: 8px; }
}
Затрагиваются элементы img в оболочке .card; те, что подставлены в .card__content, остаются вне области.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. При @layer a, b; и двух обычных объявлениях равной специфичности, одно в a и одно в b, какое применится?
    • То, что в слое a, объявленном первым
    • То, что в слое b, потому что побеждает последний объявленный слой
    • То, что стоит позже в файле, слои не учитываются
  2. Обычное объявление вне любого слоя сталкивается с обычным объявлением, помещённым в слой. Итог?
    • Объявление из слоя побеждает всегда
    • Побеждает объявление вне слоёв
    • Решает специфичность селекторов
    • Браузер сообщает о конфликте
  3. В @scope (.card) to (.card__content) какие элементы могут затронуть внутренние правила?
    • Всех потомков .card без исключения
    • Потомков .card, находящихся вне .card__content (donut scope)
    • Только .card__content и её детей