CSS-архитектура
Организация стилей в проекте: каскад и специфичность, переменные-токены, БЭМ, состояния, @layer и переиспользование без дублирования.
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 |
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);
}- токены — базовые значения (цвета, размеры, интервалы);
- смысловые имена легче читать: --color-danger вместо #dc2626;
- переменные наследуются и переопределяются — это основа тем;
- дефолты задавайте в :root, переопределение — на контейнере.
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; }- элемент принадлежит блоку: card__title, не просто .title;
- модификатор — вариант блока, а не новый блок (card--featured);
- вложенность не пишется в классе: верстка страницы — родитель/ребёнок, БЭМ — логика;
- глубокая вложенность (.card .content .title) — антипаттерн для больших проектов.
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 /* только для страниц (редко) */- токены и базовые стили не зависят от компонентов;
- компоненты не дублируют базовые стили (не пишите font-size на каждый класс без нужды);
- утилиты — тонкий слой, не замена компонентам;
- CSS-модули или scoped-стили (Vue/Svelte) изолируют страницы — там правила тоже по слоям.
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>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 поддерживается во всех современных браузерах (2022+);
- не поддерживающим — аккуратный fallback (простое упорядочивание файлов).
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; }- :is() сохраняет специфичность наименее важного селектора из перечня;
- :where() вообще не даёт веса — переопределения проще;
- используйте в базовых стилях, не комбинируйте с тяжелыми ид-селекторами.
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> -->- базовый класс несёт общее, модификатор — отличие;
- не выносите в модификатор то, что у всех одинаково;
- для объединения с внешними стилями — CSS-композиция через class, не дублирование правил;
- если модификаторов больше трёх — пересмотрите модель блока.
12. Чек-лист архитектуры CSS
Ревизия стилевого слоя по пунктам даёт быстрый и полезный аудит.
- все цвета и размеры — через токены-variables, без магических литералов;
- специфичность плоская: классы, минимальные id и вложенность;
- не более одного !important в утилитах, а не для «лечения»;
- компоненты на БЭМ-классах, состояние через модификаторы/псевдоклассы;
- файлы упорядочены и подключаются от токенов к утилитам;
- нет дублей: один блок — одно правило (проверяется поиском по проекту);
- тема переключается только сменой токенов.
Что дальше
Курс завершён.