Kodokon kodokon.com

Архитектура CSS: BEM, utility-first, организация файлов

Сравни BEM и подход utility-first, их реальные компромиссы в команде, и выстрой свои файлы через слои каскада.

11 мин · 3 вопросов

Открыть этот урок в Kodokon

CSS ломается не на масштабе компонента, а на масштабе проекта: специфичность растёт только ради того, чтобы «победить» существующее правило, страшно удалять код, охват которого тебе неизвестен, стили протекают с одного экрана на другой. Архитектура CSS - это прежде всего стратегия управления специфичностью и областью действия. Две доминирующие школы, BEM и utility-first, отвечают на одну и ту же задачу противоположными путями.

BEM (Block, Element, Modifier) даёт имя каждому стилизованному узлу: .card (самостоятельный блок), .card__title (элемент, принадлежащий блоку), .card--featured (вариант блока). Соглашение гарантирует плоскую специфичность - везде один класс, никаких селекторов потомков - а значит, никаких конфликтов порядка, плюс имя, которое документирует принадлежность. Цена: приходится постоянно придумывать имена, и появляется настоящее дублирование значений (один и тот же margin-top: 16px, переписанный в двадцати блоках).

CSS
.card {
  padding: 16px;
  border-radius: 8px;
}

.card__title {
  font-size: 1.25rem;
}

.card__title--muted {
  color: #6b7280;
}

.card--featured {
  border: 2px solid var(--brand);
}
BEM: плоская специфичность, явная принадлежность

Подход utility-first (Tailwind - его главное воплощение) переворачивает логику: никаких имён, только атомарные классы с одной ответственностью, которые компонуются прямо в HTML. Выгоды измеримы: нулевые затраты на именование, стили лежат рядом с разметкой (удалил компонент - удалил и его стили), а итоговый CSS-бандл почти не растёт в размере, потому что утилиты переиспользуются. Компромиссы тоже есть: многословный HTML, дублирование, перенесённое в разметку и требующее слоя компонентов (React, Vue, частичные шаблоны), чтобы остаться DRY, и кривая обучения словарю.

HTML
<article class="flex flex-col gap-4 rounded-lg
  border border-gray-200 p-4 shadow-sm">
  <h2 class="text-lg font-semibold">Invoice</h2>
  <p class="text-sm text-gray-600">
    Paid on March 12
  </p>
</article>
Utility-first: стили живут в разметке

Эти подходы не исключают друг друга: многие зрелые команды их совмещают - утилиты для раскладки и отступов, классы компонентов (BEM или CSS Modules) для сложных паттернов и состояний. Что до организации файлов, принцип ITCSS остаётся ориентиром: порядок от общего к частному (сброс, базовые элементы, компоненты, утилиты). Нативное правило @layer превращает это соглашение в гарантию на уровне движка: приоритет между слоями не зависит от специфичности содержащихся в них селекторов.

CSS
@layer reset, base, components, utilities;

@layer base {
  h2 { font-size: 1.5rem; }
}

@layer components {
  #hero .btn { padding: 12px 24px; }
}

@layer utilities {
  .p-0 { padding: 0; }
}
Слои каскада: порядок сильнее специфичности

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Что обозначает сегмент --muted в .card__title--muted?
    • Пользовательское свойство CSS
    • Модификатор: вариант элемента card__title
    • Дочерний элемент card__title
    • Состояние, управляемое из JavaScript
  2. Что главное @layer привносит в каскад?
    • Он повышает специфичность селекторов слоя
    • Он делает порядок слоёв важнее специфичности селекторов
    • Он изолирует стили компонента так же, как это сделал бы Shadow DOM
    • Он загружает слои параллельно, чтобы ускорить отрисовку
  3. На какой компромисс ты идёшь, выбирая подход utility-first?
    • CSS-файл растёт пропорционально числу страниц
    • Высокая специфичность, которую трудно перебить
    • Многословный HTML, дедупликация которого держится на слое компонентов