CSS / Основы
Каскад и приоритеты CSS: как браузер выбирает итоговый стиль
Пошагово разбираем каскад, происхождение правил, cascade layers, специфичность, порядок и наследование без злоупотребления !important.
Содержание статьи
К одному элементу часто подходят несколько CSS-объявлений. Каскад — алгоритм, который выбирает победителя для каждого свойства. Популярное объяснение «более специфичный селектор всегда сильнее» неполно: до специфичности браузер учитывает релевантность, происхождение, важность и слои.
Простой конфликт
<p class="message">Сборка завершена</p>p { color: #344054;}.message { color: #067647;}Оба правила подходят, но класс имеет большую специфичность, поэтому текст будет зелёным.
Если селекторы равны по приоритету, побеждает объявление, расположенное позже:
.message { color: green;}.message { color: purple;}Итоговый цвет — purple.
Порядок решений каскада
Упрощённо браузер проходит этапы:
- Релевантность. Подходит ли селектор и выполняется ли условие
@media? - Происхождение и важность. Сравниваются стили браузера, пользователя и автора, обычные и
!important, а также переходы и анимации. - Cascade layers. Для правил одного происхождения учитывается порядок слоёв.
- Специфичность. Сравнивается вес селекторов внутри одного уровня.
- Близость области
@scope. Если используются scoped-правила, выигрывает ближайшая граница. - Порядок объявления. При полном равенстве побеждает более позднее правило.
Специфичность не может «перепрыгнуть» более важное происхождение или слой.
Как считать специфичность
Для практики удобно представить три группы:
- идентификаторы
#id; - классы, атрибуты и псевдоклассы;
- теги и псевдоэлементы.
#app .card p /* 1-1-1 */.card[data-state] p /* 0-2-1 */.card__text /* 0-1-0 */Сначала сравнивается количество идентификаторов, затем классов, затем типов. Большое число тегов не перевесит один класс.
Inline-стиль имеет отдельный высокий приоритет среди обычных авторских объявлений:
<p style="color: tomato">...</p>Поэтому inline-стили усложняют переопределение и редко подходят для обычной дизайн-системы.
Особые псевдоклассы
:where() всегда имеет нулевую специфичность:
:where(.article) h2 { margin-block-start: 2em;}Такое базовое правило легко переопределить классом компонента.
:is(), :not() и :has() сами не добавляют вес, но принимают специфичность наиболее сильного селектора в списке:
.card:has(.card__image) { grid-template-rows: auto 1fr;}Не прячьте внутри :is() идентификатор без необходимости: он повысит вес всего селектора.
Cascade layers
Слои позволяют заранее определить порядок групп стилей:
@layer reset, base, components, utilities;@layer base { a { color: #4f46e5; }}@layer components { .button { color: #ffffff; }}Обычные правила в более позднем слое сильнее обычных правил раннего слоя независимо от специфичности. Непослойные авторские правила имеют приоритет над обычными правилами в слоях, поэтому не добавляйте их случайно.
У !important порядок слоёв разворачивается: ранние важные слои получают преимущество. Это защищает базовые ограничения, но делает модель сложнее — используйте важные объявления редко.
!important
.button { color: white !important;}!important меняет уровень каскада, а не просто добавляет специфичность. Ответное !important запускает новый конфликт и быстро превращает стили в гонку.
Сначала попробуйте:
- снизить специфичность базового правила через
:where(); - выстроить порядок слоёв;
- удалить лишние селекторы и идентификаторы;
- поставить вариацию компонента после базы;
- исправить владельца визуального контракта.
!important может быть оправдан для редкой utility-гарантии или пользовательского override, но решение должно быть осознанным.
Наследование — не каскад между подходящими правилами
Некоторые свойства, например color и font-family, наследуются от родителя, если у элемента нет собственного каскадного значения.
.card { color: #20242c;}.card__link { color: inherit;}margin, padding, border обычно не наследуются. Ключевые слова:
inherit— взять вычисленное значение родителя;initial— использовать начальное значение свойства;unset— наследовать для наследуемого свойства, иначе взять начальное;revert— откатить текущий уровень каскада;revert-layer— откатить объявления текущего слоя.
Как отлаживать конфликт
- Выберите элемент в DevTools.
- Найдите свойство в панели Styles или Computed.
- Посмотрите, какие объявления зачёркнуты.
- Проверьте
@media, слой и наличие!important. - Сравните специфичность только после предыдущих уровней.
- Исправьте структуру правил, а не добавляйте случайный более длинный селектор.
Что запомнить
Каскад — это упорядоченный алгоритм, а специфичность — только один его этап. Проектируйте низкую специфичность, понятные слои и локальные классы компонентов. DevTools почти всегда быстрее объясняет конфликт, чем очередной !important.