CSS специфичность и приоритет селекторов: понятное руководство с примерами

Если «стили не применяются», почти всегда виноват каскад и специфичность. В этом руководстве мы системно разберём, как CSS решает, какое правило победит, научимся считать «вес» селекторов, корректно использовать !important и современные инструменты управления каскадом: :where(), :is() и @layer.
Каскад, наследование и порядок
- Наследование: некоторые свойства (цвет текста, шрифт) наследуются от предка, а, например, margin — нет.
- Специфичность: «вес» селектора определяет его силу в конфликте.
- Порядок в коде: при равной специфичности выигрывает более позднее правило (ниже в файле).
- !important: перебивает обычные правила, но уступает каскадным слоям и другим !important той же группы при равной специфичности и порядке.
Как считать специфичность
Классическая модель: a-b-c-d (inline | ID | классы/атрибуты/псевдоклассы | теги/псевдоэлементы). Чем левее число больше — тем сильнее селектор.
/* Формат: inline | ID | class/attr/pseudo-class | tag/pseudo-element */
a { /* 0 | 0 | 0 | 1 */ }
.menu a.active { /* 0 | 0 | 2 | 1 */ }
#header .menu a { /* 0 | 1 | 1 | 1 */ }
/ * inline style на элементе — 1 | 0 | 0 | 0 * /
Важно: универсальный селектор * и :where() не увеличивают специфичность, а :is() принимает специфичность самого «тяжёлого» из перечисленных селекторов внутри скобок.
Пример конфликта и его разбор
<nav id='header'>
<ul class='menu'>
<li><a class='link' href='#'>Главная</a></li>
</ul>
</nav>
/* Правило 1 */
.menu a { color: #222; } /* 0|0|1|1 */
/* Правило 2 */
#header .menu .link { color: #06f; } /* 0|1|1|0 */
/* Правило 3 (идёт позже) */
.menu .link { color: #e11; } /* 0|0|2|0 */
Какой цвет будет у ссылки? Победит «Правило 2», потому что оно содержит ID (вес 0|1|1|0), а это сильнее любого количества классов из «Правила 3». Порядок в коде уже не важен — сначала сравнивается специфичность.
Наследование против специфичности
body { color: #333; } /* наследуется детьми */
.link { color: #06f; } /* перекрывает наследование */
article { font-size: 18px; } /* наследуется */
article .note { font-size: 14px; } /* локально меняем размер */
Если свойство наследуется, то любой явный селектор, даже с небольшим весом, перекроет наследуемое значение. Если не наследуется — нужно задать его на самом элементе или использовать inherit/initial/revert.
Когда (и как) можно использовать !important
- Точечно в утилитарных классах (например, .sr-only, .hidden).
- При интеграции сторонних виджетов, когда доступа к исходным стилям нет.
- Избегайте !important для «лечения симптомов». Лучше снизить специфичность конфликтующих правил.
.text-error { color: #e11 !important; }
/ * Лучше держать !important в контролируемых утилити-классах * /
:where() и :is(): снижаем и контролируем «вес»
:where() добавляет 0 к специфичности — удобно для базовых паттернов. :is() берёт максимальную специфичность из списка внутри скобок — удобно для краткости без потери контроля.
/* Базовое оформление любых кнопок в карточках без повышения веса */
.card :where(.btn) { padding: .5rem 1rem; border-radius: .5rem; }
/* Селектор короче, но специфичность определяется самым «тяжёлым» вариантом */
:is(h1, h2, h3).title { margin-bottom: .5rem; }
Каскадные слои (@layer): упорядочиваем стили по смыслу
@layer позволяет задать предсказуемый порядок целых групп стилей, независимо от специфичности внутри слоёв. Сначала объявляем порядок слоёв, затем пишем стили в них.
@layer reset, base, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
}
@layer base {
body { font: 16px/1.5 system-ui, sans-serif; }
a { color: #06f; }
}
@layer components {
.btn { background: #06f; color: #fff; }
}
@layer utilities {
.text-red { color: #e11 !important; }
}
Даже если внутри components появится селектор с высоким «весом», стили из utilities всё равно перекроют его благодаря порядку слоёв.
Типичные ошибки и как их исправить
- Избыточная вложенность: .header .menu li a.link — сложно переопределить. Сокращайте до .menu .link или .nav-link.
- Использование ID для стилизации: ID резко повышает вес. Предпочитайте классы (BEM/утилити).
- Повальная выдача !important: устраняйте причину — структуру и порядок, а не симптомы.
/ * Плохо * /
#header .menu li a.link { color: #06f; }
/ * Лучше (BEM) * /
.menu__link { color: #06f; }
.menu__link--active { color: #e11; }
Отладка в DevTools
- Выделите элемент, вкладка Styles: смотрите перечёркнутые правила и почему они проиграли (specificity/order).
- В Computed видно итоговые значения и откуда они пришли.
- Экспериментируйте: замените сложный селектор на :where(...) и проверьте применимость без повышения веса.
Практический чеклист
- Сначала проектируйте слои: reset → base → components → utilities.
- Отдавайте предпочтение классам, избегайте ID и глубокой вложенности.
- Используйте :where() для базовых паттернов, :is() — для сокращения селекторов.
- Храните !important в контролируемых утилити-классах, а не «везде».
- Подтверждайте гипотезы в DevTools, а не «на глаз».
Хотите закрепить тему каскада на реальной вёрстке и получить системное понимание CSS-основ? Загляните в Практический курс «Вёрстка сайта с нуля 2.0» — от первых блоков до уверенного владения современными приёмами.
Итоги
Специфичность — это не магия, а простая математика селекторов плюс порядок в коде. Управляйте «весом» осознанно, используйте каскадные слои и современный селекторный инструментарий — и ваши стили будут применяться предсказуемо и без «битв за !important».
-
-
Михаил Русаков
Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.