@layer

Управляем каскадными слоями своими руками.

Время чтения: больше 15 мин

Кратко

Скопировано

Директива @layer, дополнительная функция layer() и ключевое слово layer дают вам возможность управлять каскадными слоями.

Каскадные слои — способ группировки и порядок применения стилей в одном источнике. Они помогают браузерам понять, какие стили применить на сайте.

Источник в CSS — место, откуда браузер берёт CSS-правила. Это могут быть ваши стили на сайте, настройки пользователей в браузере и системе, а также браузерные стили по умолчанию. Подробнее об этой и других концепциях в статье «Принцип каскада».

Пример

Скопировано

Благодаря каскадным слоям вы напрямую управляете слоями с разными наборами CSS-правил и решаете, какие стили специфичнее и в каком порядке их применять. Так что они пригодятся для:

  • сброса браузерных стилей по умолчанию;
  • перезаписи стилей из CSS-фреймворков и библиотек;
  • стилизации компонентов;
  • хранения стилей в одном месте, например, для состояний интерактивных элементов;
  • цветовых тем, схем и поддержки разных режимов передачи цветов.

Например, вот стили с @layer для состояния кнопки при фокусе и когда она неактивна. В слое states устанавливаем стили для :disabled и :focus-visible, а в components — стили кнопки по умолчанию.

        
          
          @layer components, states;@layer components {  .button {    display: block;    min-width: 210px;    border: 2px solid transparent;    border-radius: 6px;    padding: 9px 15px;    color: #000000;    font-size: 18px;    font-weight: 300;    font-family: inherit;    transition: background-color 0.2s linear;  }  .button:hover {    background-color: #FFFFFF;    cursor: pointer;    transition: background-color 0.2s linear;  }}@layer states {  :focus-visible {    border: 2px solid #FFFFFF;    outline: none;  }  :disabled {    background-color: #DDDDDD;  }}
          @layer components, states;

@layer components {
  .button {
    display: block;
    min-width: 210px;
    border: 2px solid transparent;
    border-radius: 6px;
    padding: 9px 15px;
    color: #000000;
    font-size: 18px;
    font-weight: 300;
    font-family: inherit;
    transition: background-color 0.2s linear;
  }

  .button:hover {
    background-color: #FFFFFF;
    cursor: pointer;
    transition: background-color 0.2s linear;
  }
}

@layer states {
  :focus-visible {
    border: 2px solid #FFFFFF;
    outline: none;
  }

  :disabled {
    background-color: #DDDDDD;
  }
}

        
        
          
        
      
Открыть демо в новой вкладке

Без каскадных слоёв нам нужно задать стили классу .button с нужными псевдоклассами. При этом мы не можем хранить их в одном месте файла так, чтобы их случайно не перезаписали другие разработчики.

        
          
          .button {  /* Нужные CSS-правила */}.button:hover {  /* Нужные CSS-правила */}.button:focus-visible {  /* Нужные CSS-правила */}.button:disabled {  /* Нужные CSS-правила */}
          .button {
  /* Нужные CSS-правила */
}

.button:hover {
  /* Нужные CSS-правила */
}

.button:focus-visible {
  /* Нужные CSS-правила */
}

.button:disabled {
  /* Нужные CSS-правила */
}

        
        
          
        
      

С каскадными слоями легче победить сторонние стили. Например, из Bootstrap. Представим, что хотим изменить размер заголовка первого уровня без мучительных поисков нужных Sass-переменных.

        
          
          <h1 class="fs-1">Последние обновления</h1>
          <h1 class="fs-1">Последние обновления</h1>

        
        
          
        
      

Стили Bootstrap:

        
          
          .fs-1 {  font-size: 2.5rem !important;}
          .fs-1 {
  font-size: 2.5rem !important;
}

        
        
          
        
      

Наши стили с использованием каскадных слоёв:

        
          
          @import url("https://cdn.com/bootstrap.min.css") layer;@layer {  .fs-1 {    font-size: 3rem;  }}
          @import url("https://cdn.com/bootstrap.min.css") layer;

@layer {
  .fs-1 {
    font-size: 3rem;
  }
}

        
        
          
        
      

Со старыми возможностями CSS эту проблему приходится решать несколькими способами. Например, модификатором !important.

        
          
          .fs-1 {  font-size: 3rem !important;}
          .fs-1 {
  font-size: 3rem !important;
}

        
        
          
        
      

Как пишется

Скопировано

Есть несколько способов объявления каскадных слоёв:

  • директива @layer;
  • ключевое слово layer;
  • функция layer().

Слои могут быть с именем (именованными) и без него (анонимными).

С помощью именованных слоёв вы напрямую управляете порядком стилей или определяете его в одном месте CSS-файла.

Анонимные слои не дают другим разработчикам манипулировать правилами в них и гарантируют, что все нужные правила хранятся в одном месте файла. Это особенно полезно, когда заменяете стили из CSS-фреймворка на свои.

После компиляции стилей с каскадными слоями, вы получаете обычный CSS-код. В инструменте разработчика заметите, что ваши стили берутся из файла проекта. Также у правил указано, что это за слой и как называется. Например, «Layer typography» или стили браузера «Layer user agent stylesheet».

Панель со стилями в инструменте разработчика в Chrome.

Директива @layer

Скопировано

Для именованных слоёв укажите имя после директивы @layer, а внутри перечислите нужные правила. Имя слоя может быть любым на латинице. К примеру, application, framework, base, utilities, typography или theme. Имена должны быть уникальными и не повторяться. Исключение — слои, вложенные в другие.

        
          
          @layer any-layer-name {  /* Одно или несколько CSS-правил */}
          @layer any-layer-name {
  /* Одно или несколько CSS-правил */
}

        
        
          
        
      

Слои можно вкладывать друг в друга — группировать. Слой с другими внутри называется родительским, а вложенные в него — дочерними. Обратите внимание, что дочерний слой не может «выйти за пределы» своего родителя. Его не связать с другими дочерними и даже родительскими каскадными слоями.

Здесь слой framework — родительский, а base и theme — дочерние:

        
          
          @layer framework {  @layer base {    /* Одно или несколько CSS-правил */  }  @layer theme {    /* Одно или несколько CSS-правил */  }}
          @layer framework {
  @layer base {
    /* Одно или несколько CSS-правил */
  }

  @layer theme {
    /* Одно или несколько CSS-правил */
  }
}

        
        
          
        
      

У дочерних слоёв могут быть такие же имена, как у других за пределами его родителя. В этом примере два совершенно разных слоя с именами base и framework.base, так как второй слой base находится внутри framework:

        
          
          @layer base {  /* Одно или несколько CSS-правил */}@layer framework {  @layer base {    /* Одно или несколько CSS-правил */  }}
          @layer base {
  /* Одно или несколько CSS-правил */
}

@layer framework {
  @layer base {
    /* Одно или несколько CSS-правил */
  }
}

        
        
          
        
      

Слои можно перечислять в одном месте. Расположите их имена через запятую после директивы @layer. Тут собрали два слоя в одной строке и получили @layer layer-1, layer-2.

        
          
          @layer layer-1, layer-2;@layer layer-1 {  /* Одно или несколько CSS-правил */}@layer layer-2 {  /* Одно или несколько CSS-правил */}
          @layer layer-1, layer-2;

@layer layer-1 {
  /* Одно или несколько CSS-правил */
}

@layer layer-2 {
  /* Одно или несколько CSS-правил */
}

        
        
          
        
      

Когда перечисляете дочерние слои в одну строку, сначала укажите название родительского, а через точку — дочернего. К примеру, у нас есть родительский слой framework и дочерние base и theme. При перечислении имена детей будут framework.base и framework.theme:

        
          
          @layer framework.base, framework.theme;@layer framework {  @layer base {    /* Одно или несколько CSS-правил */  }  @layer theme {    /* Одно или несколько CSS-правил */  }}
          @layer framework.base, framework.theme;

@layer framework {
  @layer base {
    /* Одно или несколько CSS-правил */
  }

  @layer theme {
    /* Одно или несколько CSS-правил */
  }
}

        
        
          
        
      

Слои можно перечислять и внутри @layer. В этом случае название слоя снова складывается из имени родителя и ребёнка. Например, в слое framework определяем порядок применения базовых стилей base и стилей для тёмной темы theme. Сами CSS-правила храним в отдельных слоях framework.base и framework.theme:

        
          
          @layer framework {  @layer base, theme;}@layer framework.base {  /* Одно или несколько CSS-правил */}@layer framework.theme {  /* Одно или несколько CSS-правил */}
          @layer framework {
  @layer base, theme;
}

@layer framework.base {
  /* Одно или несколько CSS-правил */
}

@layer framework.theme {
  /* Одно или несколько CSS-правил */
}

        
        
          
        
      

В @layer можно вкладывать и другие директивы. К примеру, @media.

        
          
          @layer framework {  html {    color: #000000;    background: #FFFFFF;  }  @media (prefers-color-scheme: dark) {    html {      color: #FFFFFF;      background: #000000;    }  }}
          @layer framework {
  html {
    color: #000000;
    background: #FFFFFF;
  }

  @media (prefers-color-scheme: dark) {
    html {
      color: #FFFFFF;
      background: #000000;
    }
  }
}

        
        
          
        
      

Для анонимных слоёв указывайте только директиву @layer с нужными правилами внутри.

        
          
          @layer {  /* Одно или несколько правил */}
          @layer {
  /* Одно или несколько правил */
}

        
        
          
        
      

В анонимные слои можно вкладывать именованные. Другие разработчики всё ещё не могут манипулировать стилями в них, так как их родитель остаётся анонимным. Здесь внутри анонимного слоя находятся два дочерних base и typography:

        
          
          @layer {  @layer base {    /* Одно или несколько правил */  }  @layer typography {    /* Одно или несколько правил */  }}
          @layer {
  @layer base {
    /* Одно или несколько правил */
  }

  @layer typography {
    /* Одно или несколько правил */
  }
}

        
        
          
        
      

Как работает

Скопировано

На то, в каком порядке применяются стили слоёв, влияет порядок их расположения в файле или списке. Также есть особенности поведения CSS-правил и внутри них.

В слое с CSS-правилами и ещё одним @layer выигрывают отдельные правила. В этом примере текст параграфа будет чёрным (#000000), так как селектор с этим правилом за пределами вложенного слоя:

        
          
          <p class="text--pink">  Неограниченный семиозис симулякров.</p>
          <p class="text--pink">
  Неограниченный семиозис симулякров.
</p>

        
        
          
        
      
        
          
          @layer typography {  /* Я победил 🥇 */  p {    color: #000000;  }  /* Я проиграл */  @layer content;}@layer typography.content {  p {    color: #FFFFFF;  }}
          @layer typography {
  /* Я победил 🥇 */
  p {
    color: #000000;
  }

  /* Я проиграл */
  @layer content;
}

@layer typography.content {
  p {
    color: #FFFFFF;
  }
}

        
        
          
        
      
Открыть демо в новой вкладке

Чтобы избежать путаницы, можно всегда выносить такие правила за пределы @layer:

        
          
          /* Я проиграл */@layer typography {  @layer content;}@layer typography.content {  p {    color: #FFFFFF;  }}/* Я победил 🥇 */p {  color: #000000;}
          /* Я проиграл */
@layer typography {
  @layer content;
}

@layer typography.content {
  p {
    color: #FFFFFF;
  }
}

/* Я победил 🥇 */
p {
  color: #000000;
}

        
        
          
        
      

Когда правила не вложены внутрь @layer, они всегда побеждают. Например, тут текст будет чёрным, несмотря на стили для класса .text--pink в @layer:

        
          
          <p class="text--pink">  Хроносинкластический инфундибулум.</p>
          <p class="text--pink">
  Хроносинкластический инфундибулум.
</p>

        
        
          
        
      
        
          
          /* Я проиграл */@layer typography {  .text--pink {    color: #F498AD;  }}/* Я победил 🥇 */p {  color: #000000;}
          /* Я проиграл */
@layer typography {
  .text--pink {
    color: #F498AD;
  }
}

/* Я победил 🥇 */
p {
  color: #000000;
}

        
        
          
        
      
Открыть демо в новой вкладке

Отдельное правило применится даже если расположено выше @layer, так что текст всё ещё чёрный:

        
          
          /* Я победил 🥇 */p {  color: #000000;}/* Я проиграл */@layer typography {  .text--pink {    color: #F498AD;  }}
          /* Я победил 🥇 */
p {
  color: #000000;
}

/* Я проиграл */
@layer typography {
  .text--pink {
    color: #F498AD;
  }
}

        
        
          
        
      

А что, если в слоях есть правила с модификатором !important? В этом случае порядок определяется так:

  1. Слой с @layer со свойством !important, который расположен выше других.
  2. CSS-правило вне @layer.
  3. Слой с @layer без свойств с !important.

Здесь побеждает розовый цвет текста с модификатором !important из слоя typography:

        
          
          <p class="text--pink">  Мутуализм, комменсализм, паразитизм.</p>
          <p class="text--pink">
  Мутуализм, комменсализм, паразитизм.
</p>

        
        
          
        
      
        
          
          /* Я победил 🥇 */@layer typography {  .text--pink {    color: #F498AD !important;  }}/* Я проиграл */p {  color: #000000 !important;}/* Я тоже проиграл */@layer typography {  p {    color: #FFFFFF !important;  }}
          /* Я победил 🥇 */
@layer typography {
  .text--pink {
    color: #F498AD !important;
  }
}

/* Я проиграл */
p {
  color: #000000 !important;
}

/* Я тоже проиграл */
@layer typography {
  p {
    color: #FFFFFF !important;
  }
}

        
        
          
        
      
Открыть демо в новой вкладке

Когда перечисляете слои в одну строку, важен их порядок. Первый слой в списке перезаписывается слоем после него и так далее.

В примере текст параграфа <p> снова будет чёрным, так как слой layer-2 находится после layer-1 и поэтому выше в порядке представления:

        
          
          @layer layer-1, layer-2;/* Я проиграл */@layer layer-1 {  p {    color: #FFFFFF;  }}/* Я победил 🥇 */@layer layer-2 {  p {    color: #000000;  }}
          @layer layer-1, layer-2;

/* Я проиграл */
@layer layer-1 {
  p {
    color: #FFFFFF;
  }
}

/* Я победил 🥇 */
@layer layer-2 {
  p {
    color: #000000;
  }
}

        
        
          
        
      
Открыть демо в новой вкладке

Даже если повысить специфичность параграфа из layer-1 с помощью id, layer-2 всё равно победит. Так что текст в параграфе останется чёрным:

        
          
          @layer layer-1, layer-2;/* Я проиграл */@layer layer-1 {  p#light {    color: #FFFFFF;  }}/* Я победил 🥇 */@layer layer-2 {  p {    color: #000000;  }}
          @layer layer-1, layer-2;

/* Я проиграл */
@layer layer-1 {
  p#light {
    color: #FFFFFF;
  }
}

/* Я победил 🥇 */
@layer layer-2 {
  p {
    color: #000000;
  }
}

        
        
          
        
      

Перечисление дочерних слоёв в одну строку работает так же. В этом примере есть родитель framework и два ребёнка framework.base и framework.theme. Текст параграфа снова будет чёрным, так как framework.theme в списке находится после framework.base:

        
          
          @layer framework.base, framework.theme;@layer framework {  /* Я победил 🥇 */  @layer theme {    p {      color: #000000;    }  }  /* Я проиграл */  @layer base {    p {      color: #FFFFFF;    }  }}
          @layer framework.base, framework.theme;

@layer framework {
  /* Я победил 🥇 */
  @layer theme {
    p {
      color: #000000;
    }
  }

  /* Я проиграл */
  @layer base {
    p {
      color: #FFFFFF;
    }
  }
}

        
        
          
        
      
Открыть демо в новой вкладке

Для анонимных слоёв важно, где они находятся в файле. Так что вы не можете добавить их в любое место, как именованные. Ниже анонимный слой расположен перед именованным base, поэтому текст параграфа — чёрный:

        
          
          /* Я проиграл */@layer {  p {    color: #FFFFFF;  }}/* Я победил 🥇 */@layer base {  p {    color: #000000;  }}
          /* Я проиграл */
@layer {
  p {
    color: #FFFFFF;
  }
}

/* Я победил 🥇 */
@layer base {
  p {
    color: #000000;
  }
}

        
        
          
        
      
Открыть демо в новой вкладке

Если расположить анонимный слой после именованного base, текст станет белым (#FFFFFF):

        
          
          /* Я проиграл */@layer base {  p {    color: #000000;  }}/* Я наконец-то победил 🥇 */@layer {  p {    color: #FFFFFF;  }}
          /* Я проиграл */
@layer base {
  p {
    color: #000000;
  }
}

/* Я наконец-то победил 🥇 */
@layer {
  p {
    color: #FFFFFF;
  }
}

        
        
          
        
      
Открыть демо в новой вкладке

Ключевое слово layer

Скопировано

Ключевое слово layer нужно, когда применяете правила анонимных слоёв при импорте стилей из другого файла. Это делают с помощью директивы @import.

Укажите слово layer после @import и пути к нужному CSS-файлу. Чтобы другие стили и разработчики точно не перезаписали правила из анонимного слоя, расположите его в самом конце файла. Этот трюк помогает собрать в одном месте самые важные и базовые стили, которые не хотите изменять ни при каких обстоятельствах.

Здесь заменяем нужные стили из файла tailwind.css на стили из анонимного слоя:

        
          
          @import url(tailwind.css) layer;@layer {  /* Одно или несколько правил */}
          @import url(tailwind.css) layer;

@layer {
  /* Одно или несколько правил */
}

        
        
          
        
      

Можете расположить анонимный слой и перед строкой с @import. В этом случае приоритет этих стилей будет ниже, чем у слоёв за ними.

        
          
          @layer {  /* Одно или несколько правил */}@import url(tailwind.css) layer;
          @layer {
  /* Одно или несколько правил */
}

@import url(tailwind.css) layer;

        
        
          
        
      

Правила из одного анонимного слоя можно применять несколько раз. Давайте используем стили из анонимного слоя в файлах base-forms.css, base-links.css и base-buttons.css:

        
          
          @import url(base-forms.css) layer;@import url(base-links.css) layer;@import url(base-buttons.css) layer;@layer {  /* Одно или несколько правил */}
          @import url(base-forms.css) layer;
@import url(base-links.css) layer;
@import url(base-buttons.css) layer;

@layer {
  /* Одно или несколько правил */
}

        
        
          
        
      

Функция layer()

Скопировано

С помощью функции layer() вы обращаетесь к конкретному слою при импорте стилей из одного файла в другой.

Представим, что нужно добавить свои базовые стили для сайта, на котором уже используем CSS-фреймворк, например, Bootstrap. Создадим слой base с нужными CSS-правилами и применим их в bootstrap.css:

        
          
          @import url(bootstrap.css) layer(base);@layer base {  /* Одно или несколько правил */}
          @import url(bootstrap.css) layer(base);

@layer base {
  /* Одно или несколько правил */
}

        
        
          
        
      

layer() даёт повторно использовать стили из одного и того же именованного слоя. Здесь стили из base объединяются с другими из файлов headings.css и links.css:

        
          
          @import url(buttons.css) layer(base);@import url(links.css) layer(base);@layer base {  /* Одно или несколько правил */}
          @import url(buttons.css) layer(base);
@import url(links.css) layer(base);

@layer base {
  /* Одно или несколько правил */
}

        
        
          
        
      

Функция также пригодится, когда нужен определённый дочерний слой внутри анонимного. Представим, что добавляем свои базовые стили для сайта, на котором снова используем Bootstrap. Создадим дочерний слой base внутри анонимного и обратимся прямо к нему с помощью layer(base):

        
          
          @import url(bootstrap.css) layer(base);@layer {  @layer base {    /* Одно или несколько правил */  }}
          @import url(bootstrap.css) layer(base);

@layer {
  @layer base {
    /* Одно или несколько правил */
  }
}

        
        
          
        
      

Всё вместе

Скопировано

Директиву, функцию и ключевое слово можно совмещать. Так вы сможете точнее определить порядок слоёв в одном месте перед объявлением стилей.

В начале файла перечислим слои после @layer. Расположим их так, чтобы у слоя tailwind для перезаписи сторонних стилей был более низкий приоритет, а у application для собственных стилей — самый высокий. На второй строке импортируем стили из tailwind.css и перезапишем нужные слоем tailwind с помощью layer(). После этого уже определим нужные нам CSS-правила.

        
          
          @layer default, tailwind, application;@import url(tailwind.css) layer(tailwind);@layer default {  /* Одно или несколько правил */}@layer application {  /* Одно или несколько правил */}@layer tailwind {  /* Одно или несколько правил */}
          @layer default, tailwind, application;
@import url(tailwind.css) layer(tailwind);

@layer default {
  /* Одно или несколько правил */
}

@layer application {
  /* Одно или несколько правил */
}

@layer tailwind {
  /* Одно или несколько правил */
}

        
        
          
        
      

@import можно разместить между строками с @layer. Результат будем одинаковым.

        
          
          @layer default;@import url(tailwind.css) layer(tailwind);@layer application;@layer default {  /* Одно или несколько правил */}@layer application {  /* Одно или несколько правил */}@layer tailwind {  /* Одно или несколько правил */}
          @layer default;
@import url(tailwind.css) layer(tailwind);
@layer application;

@layer default {
  /* Одно или несколько правил */
}

@layer application {
  /* Одно или несколько правил */
}

@layer tailwind {
  /* Одно или несколько правил */
}

        
        
          
        
      

Если нужно применить несколько слоёв в импортированном файле, перечислите их через запятую на одной строке с @import. Спецификация запрещает импортировать другие файлы после однострочного @layer.

        
          
          @layer default;@import url(frameworks.css) layer(bootstrap), layer(tailwind);@layer application;@layer default {  /* Одно или несколько правил */}@layer application {  /* Одно или несколько правил */}@layer bootstrap {  /* Одно или несколько правил */}@layer tailwind {  /* Одно или несколько правил */}
          @layer default;
@import url(frameworks.css) layer(bootstrap), layer(tailwind);
@layer application;

@layer default {
  /* Одно или несколько правил */
}

@layer application {
  /* Одно или несколько правил */
}

@layer bootstrap {
  /* Одно или несколько правил */
}

@layer tailwind {
  /* Одно или несколько правил */
}

        
        
          
        
      

Как понять

Скопировано

Каскадные слои в CSS похожи на слои из редакторов изображений вроде PhotoShop. Для картинок можно добавить несколько слоёв, чтобы гибче управлять разными эффектами и фильтрами. Так как слои прозрачные и не смешиваются друг с другом, легко исправить ошибку или добавить новый эффект без изменения старого.

@layer, layer и layer() решают проблемы со стилями без изменения селекторов, а также помогают избежать конфликты между разными слоями из-за их положения в CSS-файлах. Часто эти проблемы появляются из-за нескольких источников стилей. Например, фреймворков и библиотек, которые в основном используют в больших проектах.