Методологии CSS: BEM и альтернативы под живым проектом
Стили разрослись, любое изменение цепляет соседний блок, а имена классов повторяются в трёх местах. Разбираем BEM, утилитарный подход и CSS Modules — что каждый выигрывает и чем платит.

Проект живёт полтора года, файл styles.css перевалил за шесть тысяч строк. Вы правите отступ у карточки товара — ломается блок отзывов, потому что оба зацепились за .card .title. Специфичность выросла до #page .content .card ul li a, и чтобы что-то переопределить, приходится дописывать ещё один селектор. Это не проблема CSS как языка — это отсутствие методологии. Ниже три подхода, которые решают её по-разному, и честная цена каждого.
Речь о том, как организовать имена и структуру стилей на проекте среднего размера — от лендинга до магазина. Препроцессоры (Sass, Less) и CSS-in-JS для React упоминаю только там, где это меняет выбор; полный разбор сборки — отдельная тема.
Откуда берётся боль
CSS по умолчанию глобален. Любой класс виден из любого места, а правила накладываются по каскаду и специфичности. Пока файл маленький, это удобно. Когда над стилями работают несколько человек и блоков становятся сотни, начинаются три типовых сбоя:
- Коллизии имён. Два разработчика назвали блок
.header— стили перетекают друг в друга. - Гонка специфичности. Чтобы переопределить
.nav ul li a, пишут ещё длиннее, дальше —!important, дальше править уже страшно. - Мёртвый код. Никто не знает, используется ли класс
.promo-old, и его боятся удалять.
Методология — это набор договорённостей, которые заранее убирают эти три сбоя. Разберём три рабочих ответа.
BEM: имена вместо вложенности
BEM (Block, Element, Modifier) переносит структуру из вложенности селекторов в имя класса. Вместо .card .title вы пишете .card__title. Селектор остаётся плоским — один класс, специфичность 0-1-0, переопределить легко.
- Блок — самостоятельная сущность:
card,menu,search-form. - Элемент — часть блока, отдельно не живёт:
card__title,card__price. Отделяется двумя подчёркиваниями. - Модификатор — вариант блока или элемента:
card--featured,button--disabled. Отделяется двумя дефисами.
<div class="card card--featured">
<h3 class="card__title">Кофемолка</h3>
<span class="card__price">4 900 ₽</span>
<button class="card__button card__button--disabled">В корзину</button>
</div>
/* Все селекторы плоские — никакой гонки специфичности */
.card { border: 1px solid #e0e0e0; border-radius: 8px; }
.card--featured { border-color: #ff6b00; }
.card__title { font-size: 18px; margin: 0 0 8px; }
.card__price { font-weight: 700; }
.card__button--disabled { opacity: 0.5; pointer-events: none; }
Главное правило, которое нарушают чаще всего: элемент не вкладывается в элемент в имени. Не card__body__title, а card__title. Иерархия живёт в HTML, а не в классе.
Читаемость BEM в том, что по классу card__price--old вы без открытия HTML знаете: это цена, она внутри карточки, и это её зачёркнутый вариант. Имя — документация.
Цена BEM — длина. Классы получаются громоздкими, а на маленьком проекте это выглядит избыточно. С препроцессором Sass боль частично снимает амперсанд:
// Sass: & подставляет родительский селектор
.card {
border: 1px solid #e0e0e0;
&__title { font-size: 18px; }
&--featured { border-color: #ff6b00; }
}
Утилитарный подход: классы-примитивы
Противоположная идея: не описывать блоки, а собирать вид из мелких классов, каждый из которых делает одну вещь. Именно так работает Tailwind CSS (актуальная линия — Tailwind 3.x, вышла 4.x с новым движком).
<div class="border border-gray-200 rounded-lg p-4">
<h3 class="text-lg mb-2">Кофемолка</h3>
<span class="font-bold">4 900 ₽</span>
</div>
CSS вы почти не пишете — стили уже определены в наборе утилит, а неиспользованные вырезаются на сборке. Коллизий нет, потому что нет собственных имён. Специфичность у всех классов одинаковая.
Цена — разметка распухает, и повторяющийся блок приходится либо копировать целиком, либо выносить в компонент (в React/Vue это естественно, в чистом HTML — больно). Дизайн, выходящий за рамки предзаданной шкалы отступов и цветов, требует настройки конфига или произвольных значений вида p-[13px], что уже ближе к обычному CSS.
CSS Modules: локальность по умолчанию
Подход для проектов на сборщике (Vite, webpack) и компонентном фреймворке. Вы пишете обычный CSS, но сборщик превращает имена классов в уникальные, локальные для файла: .title в Card.module.css станет чем-то вроде Card_title__a3f9.
/* Card.module.css */
.card { border: 1px solid #e0e0e0; }
.title { font-size: 18px; }
// Card.jsx
import styles from './Card.module.css';
export function Card() {
return <div className={styles.card}>
<h3 className={styles.title}>Кофемолка</h3>
</div>;
}
Коллизии невозможны в принципе: имя title в двух разных файлах превратится в два разных класса. Имена короткие — не нужен BEM-нейминг, потому что локальность обеспечивает сборщик. Точную настройку (расширение .module.css, формат генерируемых имён) сверяйте с документацией вашего сборщика — она отличается у Vite и webpack.
Цена — привязка к сборке и к компонентам. Без билд-шага это не работает, а для статичного сайта на паре HTML-страниц подключать сборщик ради этого избыточно.
Сравнение
| Критерий | BEM | Утилиты (Tailwind) | CSS Modules |
|---|---|---|---|
| Нужен сборщик | Нет | Да (сборка утилит) | Да |
| Коллизии имён | Убраны договорённостью | Нет собственных имён | Невозможны технически |
| Специфичность | Плоская, 0-1-0 | Плоская | Плоская |
| Разметка | Читаемая | Распухает от классов | Читаемая |
| Переиспользование стиля | Класс блока | Через компонент | Через компонент |
| Порог входа | Низкий | Средний (учить утилиты) | Низкий, но нужна сборка |
Когда что выбирать
- Лендинг, сайт на CMS, чистый HTML без сборки. BEM. Работает без инструментов, читается, легко отдать верстальщику.
- Приложение на React/Vue со сборщиком. CSS Modules или утилиты. Локальность и переиспользование берёт на себя компонентная модель.
- Быстрое прототипирование и команда, привыкшая к Tailwind. Утилиты. Скорость набора выше, но заложите время на вынос повторов в компоненты.
Смешивать тоже нормально: BEM-структура для крупных блоков плюс горстка утилитных классов для отступов — частая рабочая связка. Плохо, когда в одном проекте живут три подхода без границы между ними.
Что ломается на практике
- BEM превращается в кашу, когда элементы вкладывают в имена:
form__group__input__label. Держите не более одного уровня элемента. - Утилиты дублируются десятками одинаковых наборов классов — это сигнал, что пора выносить компонент, а не искать «где бы это переиспользовать через @apply».
- CSS Modules протекают, если писать глобальные селекторы вроде
:global(.button)без нужды — локальность теряется, и возвращаются коллизии.
Что делать дальше
Не переписывайте существующий проект под новую методологию за один заход — это гарантированный регресс по стилям. Введите договорённость на новые блоки и оформите её коротким документом в репозитории: как называть, где граница элемента, что считать модификатором. Настройте линтер — stylelint с плагином на проверку соглашения об именах отловит нарушения на ревью автоматически. Точный набор правил и имя плагина сверяйте в документации stylelint актуальной версии, потому что состав правил меняется между мажорными релизами.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: BEM — методология: быстрый старт, Tailwind CSS — официальная документация
Частые вопросы
BEM устарел на фоне Tailwind?
А если у меня Tailwind 4, а не 3 — примеры сработают?
Можно ли обойтись вообще без методологии на маленьком проекте?
Почему нельзя просто использовать вложенные селекторы и не мучиться с BEM?
Стили не применяются в CSS Modules — где искать проблему?
@apply в Tailwind — это выход из распухшей разметки?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.