Селектор — это прицел: он решает, в какие элементы попадёт правило. Прицелов в CSS десятки, но повседневный набор невелик — и он же по совместительству определяет специфичность. Пройдём от базы к современным возможностям.
База и комбинаторы
button { } /* по тегу */
.card { } /* по классу — рабочая лошадка */
#app { } /* по id — в стилях лучше не использовать */
[disabled] { } /* по атрибуту */
[href^="https"] { } /* атрибут начинается с... (еще: $= кончается, *= содержит) */
Комбинаторы соединяют селекторы отношениями в дереве:
.card p { } /* пробел: потомок на ЛЮБОЙ глубине */
.card > p { } /* прямой ребёнок */
h2 + p { } /* сосед сразу после h2 */
h2 ~ p { } /* все последующие соседи */
Разница «потомок vs ребёнок» стреляет на вложенных структурах: .menu li покрасит и пункты вложенного подменю, .menu > li — только верхний уровень. А соседний комбинатор + — идиома «отступ между, но не снаружи»: p + p { margin-top: 1em } даёт интервал только между абзацами.
Список через запятую — это несколько независимых селекторов с одним телом: h1, h2, h3 { }. Ошибка в одном селекторе списка отменяет всё правило целиком — об этом ниже, у :is() есть лекарство.
Псевдоклассы: состояния и позиции
Псевдокласс выбирает элемент по состоянию или положению, а не по разметке.
Интерактивные состояния:
a:hover { } /* курсор над элементом */
button:active { } /* в момент нажатия */
input:focus { } /* элемент в фокусе */
button:focus-visible { } /* фокус С КЛАВИАТУРЫ — рамка табающим, без рамки кликающим */
input:disabled { }
input:checked { } /* чекбокс/радио отмечен */
Пара :focus / :focus-visible — важная деталь доступности: убирать outline совсем нельзя (клавиатурные пользователи теряют место фокуса), а :focus-visible показывает индикатор только тем, кто ходит клавиатурой.
Счёт детей:
li:first-child { }
li:last-child { }
li:nth-child(2) { } /* второй ребёнок */
li:nth-child(odd) { } /* нечётные — зебра в таблицах */
li:nth-child(3n) { } /* каждый третий */
li:nth-child(n + 4) { } /* с четвёртого и дальше */
Классические грабли — разница nth-child и nth-of-type. p:nth-child(2) означает «элемент, который является вторым ребёнком своего родителя И при этом параграф». Если первым ребёнком стоит заголовок, вторым — параграф, правило сработает; но p:nth-child(1) не найдёт ничего. p:nth-of-type(2) считает только параграфы: второй параграф среди параграфов. Не сработал nth-child — почти всегда в дереве есть «лишний» элемент, сбивающий счёт.
Отрицание: button:not(.primary) — все кнопки, кроме основной; li:not(:last-child) — «всем, кроме последнего» (тот же приём межэлементных отступов).
Псевдоэлементы: контент из ничего
Псевдоэлемент (двойное двоеточие) — не выбор существующего, а создание виртуального элемента:
.required::after {
content: " *";
color: red;
}
.quote::before { content: "« "; }
.intro::first-line { font-weight: 600; }
::selection { background: #ffe08a; } /* выделение текста */
::before/::after вставляют по виртуальному ребёнку в начало и конец элемента. Обязательное условие — свойство content (хоть пустое content: ""), без него псевдоэлемент не существует. Пустые before/after с фоном и позиционированием — стандартная техника декора: линии, иконки, оверлеи — без мусорных <div> в разметке. Ограничение: у элементов без внутреннего содержимого (<img>, <input>) они не работают.
Современный слой: :is, :where, :has
:is() сворачивает повторы и «прощает» ошибки внутри списка:
/* вместо трёх селекторов */
:is(h1, h2, h3) > a { color: inherit; }
:where() — то же, но с нулевой специфичностью: идеально для базовых стилей и библиотек, которые пользователь должен легко переопределять одним классом.
:has() — долгожданный «родительский селектор»: выбирает элемент по тому, что у него внутри:
.card:has(img) { padding: 0; } /* карточка с картинкой */
label:has(input:checked) { font-weight: 600; } /* подпись отмеченного чекбокса */
form:has(:invalid) button[type=submit] { opacity: .5; }
До :has() такие вещи требовали JavaScript. Поддержка во всех современных браузерах уже есть — но в проектах со старыми браузерами проверяйте по caniuse.
Коротко
- Повседневная база: класс + четыре комбинатора (
,>,+,~).p + p— идиома отступов «между». - Псевдоклассы — состояния и позиции:
:hover,:focus-visible(доступный фокус),:checked,:nth-child. nth-childсчитает всех детей,nth-of-type— только элементы своего типа. Сбился счёт — в дереве лишний элемент.::before/::afterсоздают виртуальные элементы; безcontentне существуют; на<img>/<input>не работают.:is()сворачивает списки,:where()— то же с нулевой специфичностью,:has()— селектор «по содержимому».
Что почитать дальше
- Каскад и специфичность — сколько «весит» каждый из этих селекторов.
- Блочная модель — следующая тема цикла.
- События DOM —
closest()и делегирование: те же селекторы на стороне JavaScript.