← назад к разделу

Половина «мистики» CSS — одна и та же ситуация: вы написали правило, а элемент выглядит по-другому. Стиль не «не работает» — он проиграл другому правилу. Механизм выбора победителя называется каскадом (та самая буква C в CSS), и он строго детерминирован. Разберём его один раз — и «мистика» закончится.

Каскад: три вопроса по порядку

Когда на одно свойство элемента претендуют несколько правил, браузер сравнивает их по трём критериям — строго по очереди, следующий смотрится только при ничьей в предыдущем:

  1. Источник и важность. Стили автора (ваши) побеждают стили браузера (user agent). !important переворачивает порядок внутри источника.
  2. Специфичность селектора — «вес» селектора, о нём ниже.
  3. Порядок в коде — при полном равенстве побеждает то, что написано позже.

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

Специфичность: считаем вес селектора

Специфичность удобно представлять тройкой чисел (A, B, C):

  • A — количество #id в селекторе;
  • B — количество классов (.btn), атрибутов ([type="text"]) и псевдоклассов (:hover);
  • C — количество тегов (button) и псевдоэлементов (::before).

Сравнение поразрядное: сначала A, при равенстве B, потом C. Сто классов не перевесят один id — разряды не «переполняются».

button          /* (0,0,1) */
.btn            /* (0,1,0) — побеждает button */
.card .btn      /* (0,2,0) — побеждает .btn */
#submit         /* (1,0,0) — побеждает всё выше */
button.btn:hover /* (0,2,1) */

Универсальный селектор * и комбинаторы (>, +, ~, пробел) веса не имеют. Инлайн-стиль style="..." сильнее любого селектора; перебить его может только !important.

Типичная битва в реальном проекте выглядит так: стиль компонента .button (0,1,0) внезапно проигрывает древнему правилу #sidebar a (1,0,1) — и вы уже пишете #sidebar a.button, наращивая гонку вооружений. Правильный выход другой: держать специфичность плоской — один класс на правило, без id в селекторах, без длинных цепочек. Тогда каскадом управляет порядок в коде, а он предсказуем. Именно эту стратегию используют методологии вроде BEM и автоматизируют CSS Modules.

Про !important достаточно одного абзаца: он не «усиливает» правило, а выходит из соревнования в отдельную лигу, где перебить его может только другой !important с большей специфичностью. Первый же !important в проекте порождает второй — лавина. Легитимные случаи: перебить чужой инлайн-стиль (виджет, письмо) и утилитарные классы, которые обязаны побеждать всегда.

Наследование: что перетекает само

Часть свойств элемент получает от родителя без всяких правил: текстовыеcolor, font-*, line-height, text-align — наследуются; коробочныеmargin, padding, border, background, width — нет. Логика проста: текст внутри карточки должен выглядеть как текст карточки, но рамка карточки не должна появляться у каждого абзаца внутри.

Поэтому базовая типографика задаётся один раз на body и перетекает всюду сама:

body {
  font-family: Inter, sans-serif;
  color: #1a1a2e;
  line-height: 1.5;
}

Управлять наследованием можно явно: inherit (возьми у родителя — даже для ненаследуемых), initial (значение по спецификации), unset (наследуемое — унаследуй, прочее — сбрось), revert (вернись к браузерному стилю).

CSS-переменные: наследование на службе

Кастомные свойства — значения, которые вы объявляете сами и переиспользуете через var():

:root {
  --color-accent: #2f5b4f;
  --space: 8px;
}
.button {
  background: var(--color-accent);
  padding: var(--space) calc(var(--space) * 2);
}

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

.dark-theme { --color-accent: #8fd3c4; } /* тёмная тема одним правилом */

И они живут в рантайме — их видно и можно менять из JavaScript, они анимируются, работают в медиазапросах. Темизация, дизайн-токены, локальные настройки компонента — всё это каскад и наследование, работающие на вас, а не против.

Коротко

  • Конфликт стилей решается по очереди: источник/важность → специфичность → порядок в коде. Следующий критерий — только при ничьей.
  • Специфичность — тройка (id, классы/атрибуты/псевдоклассы, теги). Поразрядное сравнение: сто классов не перевесят один id.
  • Держите специфичность плоской: один класс на правило, без id — и каскадом управляет предсказуемый порядок кода.
  • !important — отдельная лига, из которой нет выхода; для перебивки чужих инлайн-стилей, не для повседневности.
  • Наследуются текстовые свойства, коробочные — нет. Базовая типографика — один раз на body.
  • CSS-переменные наследуются и живут в рантайме: темы и токены — их родная задача.

Что почитать дальше

  • Селекторы — из чего складывается та самая специфичность.
  • Стили в React-приложении — как CSS Modules и токены снимают проблему каскада организационно.
  • Блочная модель — следующий фундаментный блок: из чего состоит размер элемента.