Селекторы CSS: простые и сложные

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

Селекторы CSS:
простые и сложные

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

Никита
Дубко

Расскажи мне
Узбагойся
CSS logo
@media (prefers-color-scheme: dark) {
    html {
        color-scheme: dark;
    }
}
Если Найди Примени

Найди = Селектор

Selector Level 4
MDN Web Docs logo
Chromium Code Search

Простые примеры 👍

Нет доступа к разметке 😢

Wordpress

Поехали 🚗

Дисклеймер

В докладе почти не будет упоминаний селекторов, связанных с Shadow DOM. Если вы зачем-то продолжаете читать этот длинный текст, то вот вам пара интересных фактов. Человеческий мозг имеет возможность хранить всё, что человек видит и слышит в течение всей жизни. Улитки могут спать три года, не употребляя никакой пищи. Хочу быть улиткой.

Разрешённые символы

<div class="Pos(a) Bgc(#0280ae) W(120px) H(90px)"></div>
<div class="C(#0280ae) BdB Bdc(#0280ae) P(10px)">
    Lorem ipsum
</div>

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

npm i @bramus/specificity

specificity "header:where(#top) nav li, #doormat"
(0,1,3)
(1,0,0)

bramus/specificity

Сортировка ⬇️

Алло, мы с нижнего этажа, у вас стили протекают

(255, 255, 255)

(255,255,255) is the Highest Specificity

(1023, 1023, 1023)

(255,255,255) is the Highest Specificity

База 🧱

#id
(1, 0, 0)

.class
(0, 1, 0)

div
(0, 0, 1)

*
(0, 0, 0)

@namespace svg url('http://www.w3.org/2000/svg');

a {
    color: orangered;
}

svg|a {
    fill: blueviolet;
}
@namespace

[attr]
(0, 1, 0)

[a=v]

[a~=v]

[a|=v]

[a^=v]

[a$=v]

[a*=v]

[a=v i]

[a=v s]

[id=some]
(0, 1, 0)

[class~=some]
(0, 1, 0)

Комбинаторы 🪡

.parent .child

,

.parent > .child

.child + .child
vs
.child ~ .child

CSS Nesting

.parent {
    color: tomato;

    .child {
        color: violet;

        &:hover {
            color: violetblue;
        }
    }
}
CSS Nesting

::псевдоэлементы

::before
vs
:before

Совместимость с CSS Level 1/2

Домашнее задание 📚

span::before {
    content: "ⓘ" / "Дополнительная информация:";
}
::before

::marker

Safari — только цвет и размер шрифта

::cue

<video src="video.mp4">
    <track default kind="captions" srclang="en" src="en.vtt">
</video>
<style>
    video {
        width: 100%;
    }

    video::cue {
        font-size: 1rem;
        color: yellow;
    }
</style>

::selection

::target-text

https://mefody.dev/chunks/selection/
#:~:text=There%20is%C2%A0another%20selection

Styles of text selection

::grammar-error, ::spelling-error

Не работает в Firefox

Группа
::view-transition

::view-transition
    └─ ::view-transition-group(root)
        └─ ::view-transition-image-pair(root)
            ├─ ::view-transition-old(root)
            └─ ::view-transition-new(root)
По-настоящему красивые переходы средствами браузера
По-настоящему красивые переходы средствами браузера

... a Doughnut Chart Output for a Range Input

::file-selector-button

::highlight()

Жутко виснет 🥲

Syntax Highlighting code snippets with Prism and the Custom Highlight API
Highlight API

:псевдоклассы

Домашнее задание 📚

:focus-visible

.button:focus {
    outline: none;
}

.button:focus-visible {
    border: 2px solid #FFFFFF;
    outline: none;
}
:focus-visible

:focus-within

:target

<a href="#target">К цели!</a>

<div id="target">А вот и я!</div>

⚠️ :target-within

:checked, :indeterminate

:in-range,
:out-of-range

:read-only,
:read-write

Валидация ✅

:empty

div:empty {
    outline: 2px solid deeppink;
    background: deeppink;
    height: 1em;
}

:default

:autofill

input:-internal-autofill-selected {
    appearance: menulist-button;
    background-image: none !important;
    background-color: light-dark(
        rgb(255, 235, 153),
        rgba(70, 90, 126, 0.4)
    ) !important;
    color: fieldtext !important;
}

Safari и мультимедиа

:defined

:scope

:popover-open

:lang()

<p lang="de">Wie spät ist es?</p>
<p lang="ru">Который час?</p>
<style>
    :lang(ru) {
        font-family: SomeCyrillicFont, sans-serif;
    }
    :lang(de) {
        font-family: SomeGermanFont, sans-serif;
    }
</style>

:dir()

nav {
    text-align: right;
}

nav:dir(rtl) {
    text-align: left;
}
RTL Styling 101

:only-child,
:only-of-type

:first-child,
:first-of-type,
:last-child,
:last-of-type

:not()

:not(#id)
(1, 0, 0)

:not(.class)
(0, 1, 0)

.card {
    margin-right: 2em;
}
.card:last-child {
    margin-right: 0;
}






.card:not(:last-child) {
    margin-right: 2em;
}

:is(#id)
(1, 0, 0)

article h1,
article h2,
article h3,
article h4 {
    font-weight: bold;
}

article :is(h1, h2, h3, h4) {
    font-weight: bold;
}
article h1,
article h2,
article h3,
article h4 {
    font-weight: bold;
}

article :where(h1, h2, h3, h4) {
    font-weight: bold;
}

:where(#id)
(0, 0, 0)

:nth-child()

:nth-child(2n+1 of li.test)

:nth-last-child(3n+2)

:nth-last-child(6):first-child {
    background: tomato;
}
a:nth-last-child(6):first-child,
a:nth-last-child(6):first-child ~ a {
    background: tomato;
}
Logical Styling Based on the Number of Given Elements

:nth-of-type(3n+2)

:nth-last-of-type(3n+2)

:has()

.card:has(
    :any-link:not[href^="https://mefody.dev/"]
) {
    background: maroon;
}
CSS :has() Interactive Guide
<article>
    <div>
        <p>Я параграф. Красивый такой параграф.</p>
    </div>
</article>
<style>

    div:has(article p) {
        font-weight: bold;
    }
</style>
Интересный случай с селекторами
<article>
    <div>
        <p>Я параграф. Красивый такой параграф.</p>
    </div>
</article>
<style>
    /* ❌ */
    div:has(article p) {
        font-weight: bold;
    }
</style>
Интересный случай с селекторами
<article>
    <div>
        <p>Я параграф. Красивый такой параграф.</p>
    </div>
</article>
<style>
    /* ✅ */
    div:has(:is(article p)) {
        font-weight: bold;
    }
</style>
Интересный случай с селекторами
const ParentComponent: FC = (items) => {
    let size = 'default';

    if (items.length >= 6) {
        size = 'big';
    }

    return <ChildComponent size={size} />;
}
.cards {
    grid-template-columns: repeat(2, 1fr);
}

.cards.cards_size_big {
    grid-template-columns: repeat(3, 1fr);
}
.cards {
    grid-template-columns: repeat(2, 1fr);
}

.cards:has(.card:nth-child(6)) {
    grid-template-columns: repeat(3, 1fr);
}

JS — наполнение,
CSS — стилизация

ul:has(
    > :nth-child(7)
):has(
    > :nth-child(-n+9):last-child
)
Style a parent element based on its number of children using CSS :has()
.img:has(+ p) {
    margin-bottom: 2em;
}
.card:has(.button:hover) {
    /* TODO */
}
Calendar Styling with CSS :has()
.bucket:not(:has(.item)) {
    /* Корзина пуста */
}
CSS-квиз

ul:has(li:has(a))

Светлое будущее

column||cell

<table>
    <colgroup>
        <col span="2">
        <col>
    </colgroup>
    <tbody>
        <tr>
            <td>1.1</td>
            <td>1.2</td>
            <td>2</td>
        </tr>
    </tbody>
</table>
Column combinator
Request for developer feedback: customizable select
Customizable <select> element

Как ещё применять

debug.css

REVENGE.CSS

form:not([action])::after {
    content: 'Forms must have action attributes.' !important;
}

[tabindex]:not([tabindex="0"]):not([tabindex="-1"])::after {
    content: 'Do not disrupt the natural tab order.' !important;
}

🧠 ct.css

head,
head script,
head script[type]:not([type="text/javascript"]) {
    display: block;
}

head script[src]::before {
    content: "[Blocking Script – " attr(src) "]";
}

head style:not(:empty) ~ script::before {
    content: "[JS blocked by CSS – " attr(src) "]";
}
web-platform-tests dashboard
«Веб-стандарты»

Выбирайте осознанно!

mefody.dev/talks/css-selectors/
@dark_mefody
t.me/mefody_dev QR-код на голосование