Половина «мистики» CSS — одна и та же ситуация: вы написали правило, а элемент выглядит по-другому. Стиль не «не работает» — он проиграл другому правилу. Механизм выбора победителя называется каскадом (та самая буква C в CSS), и он строго детерминирован. Разберём его один раз — и «мистика» закончится.
Каскад: три вопроса по порядку
Когда на одно свойство элемента претендуют несколько правил, браузер сравнивает их по трём критериям — строго по очереди, следующий смотрится только при ничьей в предыдущем:
- Источник и важность. Стили автора (ваши) побеждают стили браузера (user agent).
!importantпереворачивает порядок внутри источника. - Специфичность селектора — «вес» селектора, о нём ниже.
- Порядок в коде — при полном равенстве побеждает то, что написано позже.
Отсюда сразу два практических следствия. Первое: дефолтный вид кнопки или ссылки — это тоже 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 и токены снимают проблему каскада организационно.
- Блочная модель — следующий фундаментный блок: из чего состоит размер элемента.