Слоистый CSS со взбитыми сливками

Никита Дубко, HR Tech Яндекса

Слоистый CSS со взбитыми
сливками

Никита Дубко, HR Tech Яндекса

Никита Дубко

Веб-стандарты

Уютный проект

🚂 БЭМ

.block {
    color: red;
}

.block__element {
    width: 100500px;
}

.block--modifier {
    color: tomato;
}
БЭМ

Специфичность

ID :pseudo, .class, [attr] ::pseudo, element #header
:not(#id)
:hover
.card
[hidden]
::before
span

(1,0,0) > (0,0,1)

(2,1,1) > (1,1,1)

.card {
    background-color: #eee;
}

.card__title {
    color: #000;
}

.card--brand {
    background-color: tomato;
}

.card--brand .card__title {
    color: #fff;
}
.card { /* (0, 1, 0) */
    background-color: #eee;
}

.card__title { /* (0, 1, 0) */
    color: #000;
}

.card--brand { /* (0, 1, 0) */
    background-color: tomato;
}

.card--brand .card__title { /* (0, 2, 0) */
    color: #fff;
}
@import "global.css";

@import "blocks/card.css";
@import "blocks/article.css";
@import "blocks/input.css";
А давайте добавим модный
CSS-фреймворк!
Кто-то в команде
.input-group >
:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
    margin-left: calc(var(--bs-border-width) * -1);
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}
Я никому ничего не должен!
Автор библиотеки

!important

.block.block.block.block.block {
    --my-specificity: is-ok;
}
.block[class] {
    --my-specificity: is-ok;
}
.block:not(#wtf) {
    --my-specificity: is-ok;
}
Stefan Judis

Origin

a:-webkit-any-link {
    color: -webkit-link;
    cursor: pointer;
    text-decoration: underline;
}
В случае возникновения конфликтов последнее слово должно быть за пользователем, <...>.
Håkon Lie

Origin

⚠️ !important не увеличивает специфичность

!important изменяет «слой»

Context

CSS Selector Specificity
.navbar-inverse .navbar-nav > li > a {
    color: #9d9d9d;
}

.link { color: red; }

.nav-link { color: lightsteelblue; }
a:-webkit-any-link { /* Browser style */
    color: -webkit-link;
    cursor: pointer;
    text-decoration: underline;
}

⬇️ Сверху вниз

Селектор Свойство Значение
.navbar-inverse .navbar-nav > li > a color #9d9d9d
.link color red
.nav-link color lightsteelblue
a:-webkit-any-link color -webkit-link
a:-webkit-any-link cursor pointer
a:-webkit-any-link text-decoration underline
ID :pseudo,
.class,
[attr]
::pseudo,
element
Origin
.navbar-inverse .navbar-nav > li > a 0 2 2 Dev
.link 0 1 0 Dev
.nav-link 0 1 0 Dev
a:-webkit-any-link 0 1 1 Browser

⬇️ Сверху вниз

Селектор Свойство Значение Вес
.navbar-inverse .navbar-nav > li > a color #9d9d9d 0,2,2
.link color red 0,1,0
.nav-link color lightsteelblue 0,1,0
a:-webkit-any-link color -webkit-link 0,1,1
a:-webkit-any-link cursor pointer 0,1,1
a:-webkit-any-link text-decoration underline 0,1,1

⬆️ Снизу вверх

Селектор Свойство Значение Вес
.nav-link color lightsteelblue 0,1,0
.link color red 0,1,0
.navbar-inverse .navbar-nav > li > a color #9d9d9d 0,2,2
a:-webkit-any-link color -webkit-link 0,1,1
a:-webkit-any-link cursor pointer 0,1,1
a:-webkit-any-link text-decoration underline 0,1,1

🔀 Сортируем

Селектор Свойство Значение Вес
.navbar-inverse .navbar-nav > li > a color #9d9d9d 0,2,2
.nav-link color lightsteelblue 0,1,0
.link color red 0,1,0
a:-webkit-any-link color -webkit-link 0,1,1
a:-webkit-any-link cursor pointer 0,1,1
a:-webkit-any-link text-decoration underline 0,1,1
<nav class="navbar-inverse">
    <ul class="navbar-nav">
        <li><a href="#" class="nav-link">Link 1</a></li>
        <li><a href="#" class="nav-link">Link 2</a></li>
    </ul>
</nav>
.navbar-inverse .navbar-nav > li > a ✅ color #9d9d9d
.nav-link ❌ color lightsteelblue
.link ❌ color red
a:-webkit-any-link ❌ color -webkit-link
a:-webkit-any-link ✅ cursor pointer
a:-webkit-any-link ✅ text-decoration underline

@layer

@layer reset, bootstrap, utilities;
@import url('bootstrap.css') layer(bootstrap);

@layer utilities {
    .sr-only { /* ... */ }
}

@layer reset {
    *, *::before, *::after {
        box-sizing: border-box;
    }
}

p { font-size: 18px; }
@layer second {
    .sr-only { /* ... */ }
}

@layer first {
    *, *::before, *::after {
        box-sizing: border-box;
    }
}

p {
    font-size: 18px;
}
Second
First
< Undefined >
@layer
    reset,
    bootstrap,
    utilities;
Reset
Bootstrap
Utilities
< Undefined >
@import url('bootstrap.css') layer(bootstrap);

/* Undefined layer */
p {
    font-size: 18px;
}
Bootstrap
< Undefined >
@layer reset {
    *, *::before, *::after { box-sizing: border-box; }
}

@layer utilities {
    .sr-only { /* ... */ }
}

@import url('bootstrap.css')
    layer(bootstrap);

p { font-size: 18px; }
Reset
Utilities
Bootstrap
< Undefined >
@layer reset {
    *, *::before, *::after { box-sizing: border-box; }
}

@layer utilities {
    .sr-only { /* ... */ }
}

@layer bootstrap {
    .some { /* ... */ }
}

p { font-size: 18px; }
Reset
Utilities
Bootstrap
< Undefined >
<p id="id" class="class">Text</p>
@layer first, second;
@layer second {
    p { color: yellow; }
    #id { color: red; }
}
@layer first {
    .class { color: blue; }
}
p { color: green; }

⬇️ Сверху вниз

Слой Селектор Свойство Вес
second p color: yellow 0,0,1
second #id color: red 1,0,0
first .class color: blue 0,1,0
undefined p color: green 0,0,1

🔀 Сортируем слои

Слой Селектор Свойство Вес
undefined p color: green 0,0,1
first .class color: blue 0,1,0
second p color: yellow 0,0,1
second #id color: red 1,0,0

🔀 Сортируем внутри слоёв

Слой Селектор Свойство Вес
undefined p color: green 0,0,1
first .class color: blue 0,1,0
second #id color: red 1,0,0
second p color: yellow 0,0,1

⏺️ Ищем победителя

Слой Селектор Свойство Вес
undefined p color: green 0,0,1
first .class color: blue 0,1,0
second #id color: red 1,0,0
second p color: yellow 0,0,1

!important

Слои

@layer
    reset,
    components,
    utilities;

Слои

@layer
    reset,
    components,
    utilities;

🗄️ Вложенность?

@layer a {
    @layer common, important;

    @layer important { /* ... */ }

    @layer common { /* ... */ }

    @layer { /* ... */ }
}
@layer a.common {
    /* ... */
}
@layer utilities {
    @layer important {
        /* ... */
    }
}

@layer utilities.common {
    /* ... */
}

revert

@layer default {
    a { color: maroon; }
}

@layer theme {
    a {
        color: var(--brand-primary, purple);
    }

    .no-theme {
        color: revert-layer;
    }
}

Вы уже используете @layers 😉

Пора сделать ещё один шаг 🚶🏻‍♂️

@import url('bootstrap.css');                 

button {
    font-size: 18px;
}
@import url('bootstrap.css') layer(bootstrap);

button {
    font-size: 18px;
}

Полезные применения

reset.css

@import url('reset.css') layer(reset);

@layer reset {
    *,
    *::before,
    *::after {
        /* Some custom reset styles */
    }
}

p { font-size: 18px; }

debug.css

@layer reset, components, debug;

@import url('debug.css') layer(debug);
@import url('reset.css') layer(reset);

@layer components {
    /* Some styles */
}

👷 CSS-архитектура

@layer
    reset,

    frameworks.bootstrap,
    framework.material,

    global,

    components.design-system,
    components.custom,

    theme.light,
    theme.dark,

    utilities;
caniuse
@csstools/postcss-cascade-layers

🤩 Уже можно

A Complete Guide to CSS Cascade Layers
CSS @layer visualizer

Всем добра!

Слайды
mefody.dev/talks/layers/

Подписаться
@dark_mefody
t.me/mefody_dev QR-код со ссылкой на голосование