Каскадность в CSS: как вычисляется специфичность (приоритет) стилей

CSS — это каскадные таблицы стилей (Cascading Style Sheets). Каскадность — это главный принцип CSS — приоритет одних правил/стилей над другими, когда одни стили перебивают другие. Это понятие также называется "специфичностью селектора".

Читайте также о специфичности: https://developer.mozilla.org/ru/docs/Web/CSS/Specificity

При вычислении приоритета браузер определяет «вес» каждого CSS-правила. Вес правила — это сумма весов каждого элемента селектора.

Таблица весов каждого элемента селектора:

Тип селектора Описание селектора Вес (число)
* универсальный селектор 0
div тег 1
::first-letter, ::before, ::after псевдоэлемент 1
.class класс 10
:hover, :not() псевдокласс 10
[атрибут="значение"] селектор атрибута 10
#content селектор по id 100
style="color:red;" стили в атрибуте style 1000
!important суффикс увеличения веса 10000

Пример подсчета веса:

#content .text p { color: red; } /* 100 + 10 + 1 = 111 */
.text p { color: blue; }         /* 10 + 1 = 11 */

Сильные (по весу) стили заменяют слабые, и элемент получает стили от самых «весомых» правил. Это и есть каскадность.

Тег <p> внутри элемента .text получит стиль color: red;, а не color: blue;, потому что число 111 больше, чем 11.

Если вес одинаковый, то применяются те стили, которые указаны позднее — ближе к концу HTML-страницы (ниже в коде).

На практике считать приоритеты не нужно, но важно понимать, как это работает и какой из элементов селектора весомее остальных.

Еще примеры подсчета веса:

* {}                        /*  0  */
li {}                       /*  1  */
li::first-line {}           /*  2  */
ul li {}                    /*  2  */
ul ol + li {}               /*  3  */
ul li.red {}                /* 12  */
li.red.level {}             /* 21  */
li:not(.red) {}             /* 11  */
li:not(.red):not(.green) {} /* 11  */

.foo {}                     /*  10 */
.foo[class] {}              /*  20 */
#t34 {}                     /* 100 */
#content #wrap {}           /* 200 */

Трюк с увеличением веса. Допустим, нужно увеличить приоритет стилей, не добавляя неуниверсальных селекторов. Это можно сделать:

  • продублировав селектор,
  • добавив селектор атрибута или псевдокласса.
.class.class  { color: blue; } /* вес = 20 */
.class[class] { color: blue; } /* вес = 20 */
.class        { color: red; }  /* вес = 10 */

img[src] {} /* вес = 11 */

В объяснении выше расчет был показан в виде чисел (десяток, соток и тысяч), потому что по моему так проще понять и проще считать (если нужно).

В реальности специфичность рассчитывается не как показано выше, а на основе четырёх уровней, которые часто обозначаются как A, B, C, D:

  • A (встроенные стили): Самая высокая специфичность.
  • B (ID): Высокая специфичность.
  • C (классы, атрибуты, псевдоклассы): Средняя специфичность.
  • D (элементы, псевдоэлементы): Низкая специфичность.

Структура оценки специфичности: (A, B, C, D)

  • A = Встроенные стили → 1, 0, 0, 0
  • B = Количество селекторов ID → 0, 1, 0, 0
  • C = Количество селекторов class, атрибутов или псевдоклассов → 0, 0, 1, 0
  • D = Количество селекторов элементов или псевдоэлементов → 0, 0, 0, 1

Например:

#content .button:hover a { color: orange; } // 0, 1, 2, 1

Приоритет @media

Медиаправила @media (max-width: 500px) {} не участвуют в подсчете приоритета (веса).
Поэтому они всегда должны располагаться ниже всех остальных правил, чтобы перебивать предыдущие правила с таким же весом (приоритетом).

Правильно:

.section { width: 100%; }

@media (max-width: 500px) {
  .section { width: 50%; }
}

Неправильно:

@media (max-width: 500px) {
  .section { width: 50%; }
}

.section { width: 100%; }