CSS-архитектура

Организация стилей в проекте: каскад и специфичность, переменные-токены, БЭМ, состояния, @layer и переиспользование без дублирования.

Уровень: средний 12 разделов 2 часа

1. Зачем архитектура стилей

Маленький проект стилизуется одним файлом, большой — десятками. Без архитектуры правила начинают конфликтовать: один — точнее, другой — позже, третий лечит «через !important». Архитектура CSS — это договорённости, по которым файлы не ломают друг друга и код легко читается.

Признак хорошей архитектуры: можно удалить половину специфичных правил, и сайт почти не изменится. Всё, что работает «потому что я написал селектор чуть ЖИРНЕЕ» — технический долг.

2. Каскад и специфичность

Браузер решает, какое правило победит, по трём факторам: специфичности селектора, порядку и важности. Знать их нужно, чтобы «сражаться» по правилам, а не !important.

/* специфичность: id > класс/атрибут/псевдокласс > тег */
#banner { color: red; }        /* (1,0,0) — сильнее всех ниже */
.page .title { color: blue; }  /* (0,2,0) */
.title { color: green; }       /* (0,1,0) */
main h1 { color: orange; }     /* (0,0,2) */

/* порядок: равные по специфичности побеждают по месту в файле */
.title { color: green; }
.title { color: blue; }        /* победит этот */
ПравилоПримерно как считается
idвысший приоритет (1,0,0)
классы, атрибуты, псевдоклассысредний (0,1,0)
теги и псевдоэлементынизший (0,0,1)
!importantперекрывает специфичность, борется только с !important
Специфичные селекторы рождают гонку перекрытий. Правило архитектуры: держите специфичность плоской (классы, без id и композиций #id .class .class), тогда порядок в файле решает, а не «вес».

3. Переменные: токены вместо магии

CSS-переменные (custom properties) — центральный инструмент архитектуры: цвет, отступ, радиус задаются один раз, а используются и переопределяются в любом месте. Именуйте их по смыслу: --color-primary, а не --blue-500.

:root {
  --color-bg: #f8fafc;
  --color-text: #0f172a;
  --color-primary: #2563eb;
  --space-1: 4px;
  --space-2: 8px;
  --space-4: 16px;
  --radius: 8px;
}

.card {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius);
  padding: var(--space-4);
}

4. Темы через переменные

Переменные дают переключение тем без дублирования правил: класс или media-условие переопределяет токены, и весь интерфейс перекрашивается. Это работает и для системной темы через prefers-color-scheme, и для ручного switch.

:root {
  --color-bg: #ffffff;
  --color-text: #111827;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #0b1220;
    --color-text: #e5e7eb;
  }
}

/* ручное переключение: html[data-theme="dark"] { … } — те же переменные */
body {
  background: var(--color-bg);
  color: var(--color-text);
  transition: background-color 0.3s, color 0.3s;
}
Ключ темы — не «переписать цвет каждого блока», а «сменить набор токенов». Если компонент вдруг не перекрасился — он использует литерал, а не переменную: такой цвет — самый частый «баг темы».

5. БЭМ: блок, элемент, модификатор

БЭМ — договорённость об именовании: блок__элемент --модификатор. Одно слово — блок, двойное подчёркивание — элемент внутри блока, двойное тире — модификатор состояния. Нейминг виден в классе, и CSS не зависит от разметки.

<div class="card card--featured">
  <p class="card__title">Заголовок</p>
  <button class="card__button card__button--primary">Отправить</button>
</div>

.card { border-radius: 12px; padding: 16px; }
.card--featured { border-color: #2563eb; }
.card__title { font-size: 1.25rem; }
.card__button { padding: 8px 14px; }
.card__button--primary { background: #2563eb; color: #fff; }
БЭМ не обязателен буквальный: важно правило «класс описывает блок, модификатор и состояние». Пользуйтесь BEM-book style (блок__элемент + .is-active), если так привычнее команде.

6. Состояния

Состояния элемента — активное меню, открытый дропдаун, ошибка поля — стилизуйте явными классами-состояниями или псевдоклассами (:hover, :checked, :disabled, :focus-visible). Состояние на самом элементе, а не «когда предок пустой».

/* стилизовано по состоянию, решений не утаиваем */
.menu__item { color: #374151; }
.menu__item.is-active { color: #2563eb; font-weight: 600; }
.menu__item:focus-visible { outline: 3px solid #2563eb; }

/* состояние «ошибка» связывается с полем, а не ловит весь form */
.input__field:user-invalid { border-color: #dc2626; }
.input__field:user-invalid + .input__error { display: block; }

7. Разбивка файлов

Даже один CSS-файл удобнее читать как логические части. В проекте файлы группируют по назначению: токены, базовые стили, компоненты, утилиты, страницы. Порядок подключения = спираль «от токенов к точечным правкам».

/* структура типичного стилевого слоя */
01_tokens.css     /* переменные, цвета, интервалы, шрифты */
02_reset.css      /* сброс (modern-normalize, уберём дефолты) */
03_base.css       /* элементы: body, a, h1–h6, p, ul */
04_components.css /* .card, .button, .form-field — не зависит от страниц */
05_utilities.css  /* .u-mt-2, .u-hidden, хелперы-исключения */
06_pages.css      /* только для страниц (редко) */

8. Утилитарные классы

Утилиты — маленькие однофункциональные классы: отступ, скрытие, выравнивание. Они полезны там, где «один раз поставить без контекста»: аватарка, короткий спан. Но увлечение утилитами превращает классы в набор флагов и читается как HTML.

.u-mt-1 { margin-top: 4px; }
.u-mt-2 { margin-top: 8px; }
.u-text-muted { color: var(--color-text-muted); }
.u-hidden { display: none !important; }
.u-flex { display: flex; }
.u-gap-2 { gap: 8px; }

<!-- использование: утилита для точечного случая -->
<a class="u-mt-2 u-text-muted" href="#">Подробнее</a>
Утилиты и БЭМ-нейминг не конфликтуют: утилиты точечные и маленькие, компоненты — крупно формованные. Но «собрать карточку из 15 утилит» — это отказ от компонентного стиля и мучительный ре-дизайн событий.

9. @layer: порядок вместо войны

@layer задаёт явный порядок слоёв: стили внутри слоя всегда уступают или побеждают по порядку слоя, независимо от специфичности. Это снимает гонку «кто позже в файле» и позволяет писать селекторы без страха.

@layer reset, base, components, utilities;

@layer base {
  a { color: var(--color-primary); }
}

@layer components {
  /* любой селектор здесь проигрывает utilities и может спокойно
     использовать ту же специфичность, что и base */
  .button { background: var(--color-primary); }
}

@layer utilities {
  .u-hidden { display: none; }
}
@layer хорош, когда команда пишет поверх чужого CSS (библиотека, ресайз). Классический пример — слоёные библиотеки пресетов (Open Props и подобные), куда свёвают свои правила.

10. :is() и :where()

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

/* до: дублирование */
.card h2, .card h3, .card p { margin: 0 0 8px; }

/* после: один селектор */
.card :is(h2, h3, p) { margin: 0 0 8px; }

/* :where() для «уступающих» стилей по умолчанию */
:where(.card, .table, .form) { font-weight: 400; }

11. Переиспользование без дублей

«Скопировать-вставить» этого класса для похожей кнопки — быстро, но каждый клон становится отдельной проблемой при изменении. Переиспользование делают токенами, базовыми классами и композицией, а не копированием.

/* решение: базовый класс + модификатор */
.button {
  padding: 8px 14px;
  border-radius: 8px;
  font-weight: 500;
  background: var(--color-primary);
  color: #fff;
}
.button--ghost {
  background: transparent;
  border: 1px solid var(--color-primary);
  color: var(--color-primary);
}

<!-- <button class="button">Сохранить</button>      -->
<!-- <button class="button button--ghost">Отмена</button> -->

12. Чек-лист архитектуры CSS

Ревизия стилевого слоя по пунктам даёт быстрый и полезный аудит.

Проверьте на практике: внесите в CSS одну правку (смените цвет primary) и убедитесь, что тема переключилась во всём интерфейсе. Если где-то старый цвет остался — там литерал, а не переменная: это ваша шпаргалка для рефакторинга.

Что дальше

Курс завершён.

← Вернуться к курсам