Урок по треку HTML и CSS 7 мин

Архитектура CSS: как не утонуть в стилях

БЭМ, CSS Modules, CSS-in-JS, утилитарный CSS, кастомные свойства и слои.

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?
  • Как работают каскадные слои и зачем они?
  • Как бы сделал темизацию приложения?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.