CSS глобален: любой селектор может задеть любой элемент страницы. Пока стилей мало, это не мешает. Когда их тысячи строк и десять авторов, начинаются войны специфичности, !important и страх что-то удалить. Архитектура CSS - набор договорённостей и инструментов, которые возвращают предсказуемость.
БЭМ: изоляция именами
Блок, элемент, модификатор:
<article class="card card--featured">
<h2 class="card__title">...</h2>
<button class="card__button card__button--primary">...</button>
</article>
Правила: один класс на селектор, без вложенности и тегов, поэтому у всех селекторов одинаковая низкая специфичность. Плюсы: читается из разметки, работает без сборки. Минусы: длинные имена, изоляция держится только на дисциплине.
CSS Modules: изоляция сборкой
/* Button.module.css */
.root { padding: 8px 16px; }
import s from './Button.module.css';
<button className={s.root} />
Сборщик превращает .root в уникальное имя вроде .Button_root__x7f2a. Конфликт имён между файлами невозможен, CSS остаётся обычным CSS и выносится в отдельные файлы без затрат в рантайме.
CSS-in-JS
Стили пишутся в JS рядом с компонентом и могут зависеть от пропсов. Два вида:
- рантайм (styled-components, Emotion): стили генерируются и вставляются в документ во время работы приложения. Цена - работа JS при рендере и сложности с серверными компонентами React, которым недоступен клиентский контекст;
- без рантайма (vanilla-extract, Linaria, Panda): стили извлекаются в обычные CSS-файлы на этапе сборки.
Утилитарный CSS
Tailwind и подобные: маленькие классы на одно свойство прямо в разметке (flex gap-4 p-6). Плюсы: не нужно придумывать имена, CSS почти не растёт с проектом, неиспользуемые классы вырезаются при сборке. Минусы: длинные строки классов, нужен дизайн-токенный конфиг и договорённости, когда выносить повторяющееся в компонент.
Кастомные свойства (CSS-переменные)
:root {
--color-accent: #2563eb;
--space-m: 16px;
}
.button { background: var(--color-accent); padding: var(--space-m); }
.theme-promo { --color-accent: #db2777; }
Отличие от переменных Sass: они живут в браузере. Наследуются по дереву, переопределяются в любом поддереве, в медиазапросе или из JS (el.style.setProperty('--x', '10px')). Значение подставляется при вычислении стилей. У var() есть запасное значение: var(--gap, 8px).
@property регистрирует типизированное свойство: синтаксис, начальное значение и наследование. Типизированное свойство можно плавно анимировать, например угол градиента.
Именно кастомные свойства лежат в основе дизайн-токенов и тем: цвета, отступы и радиусы задаются один раз и переключаются сменой значения на корне.
Каскадные слои @layer
@layer reset, base, components, utilities;
@layer reset { * { margin: 0; } }
@layer components { .btn { padding: 8px; } }
@layer utilities { .p-0 { padding: 0; } }
Порядок слоёв задаётся первым упоминанием. Для обычных объявлений побеждает более поздний слой, и специфичность между слоями не сравнивается: .p-0 из utilities перебьёт #main .btn из components. Стили вне слоёв сильнее любых слоёв. С !important порядок переворачивается: важное объявление раннего слоя сильнее важного объявления позднего.
Слои решают старую боль: подключили библиотеку, а её селекторы специфичнее ваших. Кладём библиотеку в ранний слой, и любые наши стили её перебивают.
Нативная вложенность
.card {
padding: 16px;
&:hover { box-shadow: 0 4px 12px rgb(0 0 0 / .1); }
& .title { font-weight: 600; }
@media (width >= 768px) { padding: 24px; }
}
Вложенность работает во всех основных браузерах без препроцессора. Опасность та же, что в Sass: глубокая вложенность раздувает специфичность и привязывает стили к структуре разметки.
:where для обнуления специфичности
:where(...) имеет нулевую специфичность. Удобно для сбросов и базовых стилей библиотек: :where(ul) { list-style: none; } перебьёт любой класс.
Частые ошибки
- Борются со специфичностью через
!importantвместо слоёв или упрощения селекторов. - Путают переменные Sass (подставляются при сборке) и кастомные свойства (живут в рантайме).
- Вкладывают селекторы на 4-5 уровней и получают стили, которые ломаются от любой правки разметки.
Что спросят на собесе
- Какие подходы к организации CSS знаешь, плюсы и минусы каждого?
- Как CSS Modules решают конфликт имён?
- Чем кастомные свойства отличаются от переменных Sass?
- Как работают каскадные слои и зачем они?
- Как бы сделал темизацию приложения?