Как работают каскад и наследование в CSS?
Вопрос
Как CSS решает, какое из конфликтующих правил применить к элементу, и как работает наследование свойств от родителя к потомку?
Короткий ответ
Каскад — это алгоритм, который при нескольких применимых правилах к одному элементу выбирает итоговое значение по цепочке критериев: происхождение и важность правила (!important, автор/браузер/пользователь), специфичность селектора и порядок объявления в коде. Наследование — отдельный механизм: часть свойств (в основном текстовые) по умолчанию передаётся от родителя к потомку, если явно не переопределена.
Подробный ответ
Каскад разрешает конфликт между несколькими правилами, которые применяются к одному элементу, в следующем порядке приоритета (от менее важного к более важному):
- Origin (происхождение) и важность — стили браузера по умолчанию (user agent) → стили пользователя → стили автора (наши) → стили автора с
!important→ стили пользователя с!important. На практике чаще всего сравниваются "обычные" авторские правила и!important-правила:!importantпереворачивает порядок важности и должен использоваться очень осторожно, так как ломает предсказуемость каскада. - Specificity (специфичность) — считается по селектору: инлайн-стиль > id > класс/атрибут/псевдокласс > тег. Более специфичный селектор побеждает независимо от порядка в файле. (Подробный расчёт specificity — отдельная тема.)
- Source order (порядок в коде) — при равной специфичности и важности побеждает правило, объявленное позже в каскаде (позже в файле, либо в файле, подключённом позже).
Наследование — независимый от каскада механизм: значение свойства, не заданное явно для элемента, может браться от родителя. Не все свойства наследуются:
- Наследуются по умолчанию — в основном текстовые/типографские свойства:
color,font-family,font-size,line-height,text-align,visibility,cursor. - Не наследуются по умолчанию — в основном свойства блочной модели и позиционирования:
margin,padding,border,width,height,background,position,display. Логика в том, что такие свойства специфичны для конкретного блока и наследование их сделало бы вёрстку непредсказуемой.
Управляющие ключевые слова, применимые к любому свойству:
inherit— принудительно взять значение родителя, даже если свойство обычно не наследуется.initial— сбросить к значению по умолчанию из спецификации CSS (не браузера!), напримерdisplay: initialвернёт значение по умолчанию для этого элемента согласно спеке.unset— ведёт себя какinheritдля наследуемых свойств и какinitialдля ненаследуемых — универсальный "сброс к естественному поведению".revert— откатывает к стилю предыдущего слоя каскада (обычно к стилю браузера по умолчанию), в отличие отinitial, который откатывает к спецификации CSS, а не к тому, что реально показал бы браузер без стилей автора.
Пример
/* наследование: color и font-family достаются потомкам без явного указания */
body {
color: #222;
font-family: sans-serif;
}
.card {
/* border, padding, background — НЕ наследуются, их нужно задавать явно */
border: 1px solid #ddd;
padding: 16px;
}
.card p {
/* explicit inherit: принудительно взять цвет ссылки от родителя */
color: inherit;
}
/* каскад по специфичности: .warning (класс) сильнее p (тег) */
p {
color: black;
}
.warning {
color: crimson; /* победит, т.к. специфичность класса выше тега */
}
/* !important переворачивает обычный порядок каскада — использовать осторожно */
.legacy-override {
color: blue !important;
}
/* сброс к состоянию "как будто автор ничего не писал" */
.reset-all {
all: unset;
}
Дополнительные вопросы
- Чем
all: unsetотличается отall: revert? - Как каскадные слои (
@layer) меняют порядок разрешения конфликтов между командами в большом проекте? - Почему
!importantсчитается антипаттерном и как без него решить конфликт специфичности?