Каскад и специфичность

Каскад — алгоритм, по которому браузер определяет, какое из конфликтующих CSS-правил применить к элементу. Специфичность — числовая «сила» селектора в этом алгоритме.

Зачем нужно

Когда несколько CSS-правил нацелены на один элемент и задают одно и то же свойство, браузеру нужно выбрать победителя. Понимание каскада и специфичности — ключ к предсказуемому поведению стилей и отсутствию «магических» багов.

Где используется

  • Разрешение конфликтов между стилями библиотеки и проекта
  • Переопределение стилей компонентов
  • Отладка «почему стиль не применяется?»
  • Архитектура CSS (BEM, ITCSS, CSS Layers)

Предпосылки

Алгоритм каскада

Браузер применяет стили в таком порядке приоритета (от низшего к высшему):

1. Источник стилей (Origin)

Приоритет Источник
1 (низший) User-Agent (стили браузера по умолчанию)
2 Пользовательские стили (настройки браузера)
3 Авторские стили (наш CSS)
4 Авторские !important
5 Пользовательские !important
6 (высший) User-Agent !important

2. Специфичность (Specificity)

При одинаковом источнике сравнивается «вес» селекторов.

3. Порядок появления (Order)

При одинаковой специфичности побеждает правило, объявленное последним.

Расчёт специфичности

Специфичность записывается как три числа: A-B-C

Компонент Что считается Вес
A ID-селекторы (#id) Высший
B Классы (.class), атрибуты ([attr]), псевдоклассы (:hover) Средний
C Теги (div, p), псевдоэлементы (::before) Низший

Примеры расчёта

/* 0-0-1 — один тег */
p { color: black; }

/* 0-1-0 — один класс */
.text() { color: blue; }

/* 0-1-1 — один класс + один тег */
p.text() { color: green; }

/* 1-0-0 — один ID */
#main { color: red; }

/* 1-1-1 — один ID + один класс + один тег */
#main .text() p { color: purple; }

/* 0-2-0 — два класса */
.card .title { color: orange; }

/* 0-2-1 — два класса + один тег */
div.card .title { color: pink; }

/* 1-1-3 — один ID + один класс + три тега */
#content ul li a.link { color: teal; }

Сравнение: кто побеждает?

/* 0-1-0 */
.title { color: blue; }

/* 0-0-2 */
article h1 { color: red; }

/* Побеждает .title (0-1-0 > 0-0-2) */
/* Один класс всегда > любое количество тегов */
/* 0-2-0 */
.card .title { color: blue; }

/* 0-1-1 */
h1.title { color: red; }

/* Побеждает .card .title (0-2-0 > 0-1-1) */

Особые случаи

!important — ядерная кнопка

p {
  color: red !important;
}

/* Даже это не перебьёт: */
#main .content p.text() {
  color: blue; /* Проиграет !important выше */
}

/* Перебьёт только другой !important с >= специфичностью */
.text() {
  color: green !important; /* Проиграет, если специфичность ниже */
}

Правила !important:

  • Избегайте в авторских стилях
  • Допустимо в utility-классах: .hidden { display: none !important; }
  • Допустимо для переопределения inline-стилей сторонних библиотек

inline-стили

<!-- inline-стиль бьёт любую специфичность из CSS-файла -->
<p style="color: red;">Текст</p>
/* Не сработает — inline выше */
#main .content p.text() {
  color: blue;
}

/* Сработает — !important перебивает inline */
p {
  color: green !important;
}

:where — нулевая специфичность

/* 0-0-0 — специфичность :where всегда 0 */
:where(.card .title) {
  color: blue;
}

/* 0-0-1 — перебьёт :where выше */
h2 {
  color: red;
}

:is и :not — берут специфичность аргумента

/* Специфичность = наивысший аргумент */
/* :is(#header, .nav) = 1-0-0 (по #header) */
:is(#header, .nav) a {
  color: white;
}

/* :not(.active) = 0-1-0 */
li:not(.active) {
  opacity: 0.7;
}

Порядок появления (Order of Appearance)

При одинаковой специфичности побеждает последнее правило:

.title { color: blue; }
.title { color: red; }  /* Победит — объявлено позже */

Порядок подключения файлов тоже важен:

<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="theme.css"> <!-- стили отсюда перебьют base.css -->

Визуализация каскада

Стиль применяется?
│
├─ Есть !important?
│   ├─ Да → сравни специфичность среди !important
│   └─ Нет ↓
│
├─ Сравни источник (user-agent < user < author)
│   └─ Одинаковый ↓
│
├─ Сравни CSS Layer (ранние слои < поздние слои)
│   └─ Одинаковый ↓
│
├─ Сравни специфичность (A-B-C)
│   └─ Одинаковая ↓
│
└─ Последнее правило побеждает

Частые ошибки

  1. Война !important — добавление !important порождает ещё больше !important:
    /* Так НЕ надо */
    .title { color: red !important; }
    .card .title { color: blue !important; }
    #main .card .title { color: green !important; }
    
  2. Недооценка ID — один #id перебивает любое количество классов:
    /* 1-0-0 побеждает */
    #title { color: red; }
    /* 0-10-0 проигрывает */
    .a.b.c.d.e.f.g.h.i.j { color: blue; }
    
  3. Неучтённый порядок файлов — подключение библиотеки после своих стилей перезапишет их
  4. Забыли про inline-стили — JavaScript часто добавляет style="", который бьёт CSS-файл

Практика

  • Написать 3 правила для одного элемента с разной специфичностью и предсказать результат
  • Рассчитать специфичность для 5 сложных селекторов
  • Попробовать перебить inline-стиль через !important
  • Использовать :where для стилей с нулевой специфичностью
  • Проверить каскад через DevTools: зачёркнутые стили = проигравшие

Связанные темы

Ресурсы