Именование: БЭМ и что с ним не так
Имена ломаются раньше кода. Разбираем БЭМ по-человечески, где он избыточен и как называть то, у чего названия нет.
Что ты научишься делать
- Назовёшь части интерфейса так, чтобы имена не устарели через месяц
- Поймёшь, где БЭМ помогает, а где превращается в ритуал
- Отделишь состояние от варианта — это разные вещи
#Имена ломаются первыми
Код живёт годами и переживает несколько редизайнов. Имена классов переживают их плохо.
Класс .blue-button становится враньём в тот день, когда кнопку просят сделать зелёной. Класс .left-column — когда колонку переносят вправо. .header-2 не был правдой изначально.
Вопрос не в аккуратности. Вопрос в том, что именно называет имя: внешний вид, место на странице или роль.
Так не надо
<div class="red-box big">
<p class="p14 mt10">Заказ не прошёл</p>
</div>Имена описывают, как это выглядит. При смене дизайна их придётся переименовать везде — или смириться с тем, что они врут.
Так надо
<div class="alert alert--error">
<p class="alert__text">Заказ не прошёл</p>
</div>Имена описывают роль. Внешний вид можно менять сколько угодно, разметка остаётся верной.
#БЭМ за две минуты
Блок — самостоятельная штука, которую можно перенести в другое место и она останется собой: card, menu, alert.
Элемент — часть блока, отдельно не имеющая смысла. Пишется через два подчёркивания: card__title.
Модификатор — вариант блока или элемента. Через два дефиса: card--sale, alert--error.
Странные разделители нужны, чтобы отличать границу блока от границы слова: product-card__title--big читается однозначно.
#Что с ним не так
БЭМ придумали для больших проектов с несколькими командами. В маленьких он часто превращается в ритуал.
- Длина.
product-card__price-wrapper--discounted— реальное имя из живого проекта. Читать такое тяжело. - Ложная вложенность. Соблазн писать
card__body__title, хотя элемент всегда принадлежит блоку, а не другому элементу. Правильно —card__title, сколько бы уровней разметки между ними ни было. - Элемент ради элемента. Если внутри блока один заголовок и он всегда выглядит одинаково, можно обойтись
.card h3. Это не преступление. - Модификатор вместо состояния. Об этом ниже — путаница здесь самая частая.
#Вариант и состояние — разные вещи
Модификатор — постоянное свойство: эта карточка со скидкой, эта кнопка второстепенная. Задаётся при вёрстке и не меняется.
Состояние — временное: сейчас открыто, сейчас загружается, сейчас ошибка. Меняется во время работы.
Их принято разделять по написанию: модификатор через --, состояние через приставку is- или has-.
<!-- Вариант задан один раз при вёрстке --><button class="btn btn--ghost">Отмена</button><!-- Состояние появляется и исчезает во время работы --><button class="btn btn--primary is-loading" disabled> Сохраняем…</button><!-- Ещё честнее: состояние атрибутом, а не классом --><button class="btn btn--primary" data-state="loading" disabled> Сохраняем…</button>#Как назвать то, у чего нет названия
Самый тяжёлый случай: блок есть, а слова для него нет. Обёртка вокруг двух других блоков, полоска между секциями, контейнер для выравнивания.
Работающие подходы, по порядку предпочтения.
Спроси, как это называет заказчик
Если в разговоре звучит «плашка с акцией» — значит это
promo, а неyellow-block. Язык предметной области почти всегда точнее выдуманного.Назови по задаче, а не по виду
Не
gray-strip, аdivider. Неbig-text, аlead. Задача переживёт редизайн, вид — нет.Для обёрток бери честные служебные имена
container,stack,cluster,grid— нормальные имена для блоков, у которых единственная роль в раскладке. Врать они не начнут.Если не придумывается — возможно, блок лишний
Невозможность назвать часто означает, что этот
divничего не делает. Попробуй убрать его: иногда раскладка не замечает пропажи.
#Альтернативы БЭМ
БЭМ решает одну задачу: изоляцию стилей в проекте, где нет сборки. Если сборка есть, задачу решают иначе.
| Подход | Как изолирует | Цена |
|---|---|---|
| БЭМ | Соглашением об именах | Длинные имена, дисциплина держится на людях |
| CSS-модули | Сборщик дописывает хеш к каждому классу | Нужна сборка; в инструментах разработчика имена нечитаемы |
| Утилиты | Изолировать нечего: классы атомарные | Разметка перестаёт читаться |
data-атрибуты | Селектор по атрибуту вместо класса | Селекторы длиннее, специфичность как у класса |
Ни один из них не «лучше». В проекте без сборки, как этот учебник, БЭМ остаётся самым разумным: он не требует ничего, кроме договорённости.
#Как это выглядит в реальном проекте
@layer components { /* Блок. Ни слова о том, где он лежит на странице */ .product { display: grid; gap: 8px; padding: 16px; background: var(--color-surface); border-radius: 14px; } /* Элементы — плоские селекторы, вес 0-1-0 */ .product__title { margin: 0; font-size: 1rem; } .product__price { font-weight: 700; } /* Вариант: задаётся при вёрстке */ .product--compact { padding: 10px; gap: 4px; } .product--compact .product__title { font-size: .9rem; } /* Состояние: приходит и уходит */ .product[data-state="out-of-stock"] .product__price { color: var(--color-muted); text-decoration: line-through; } /* Раскладка не в компоненте: у карточки нет внешних отступов. Расстояния задаёт тот, кто её размещает */ .catalog { display: grid; gap: 20px; }}#Когда НЕ надо
- БЭМ на лендинге в один экран. Пять блоков, которые никто не будет переиспользовать. Длинные имена тут не окупаются.
- БЭМ поверх фреймворка компонентов. Если стили и так изолированы на уровне сборки, префиксы дублируют то, что уже сделано.
- Элементы для всего подряд. Блок с восемнадцатью элементами — это, скорее всего, три блока, которые слиплись.
- Переименовывать работающий код целиком. Смена схемы именования — большая правка с риском. Вводи новую схему в новом коде, старую трогай только когда всё равно её редактируешь.
Где спотыкаются почти все
Имя описывает внешний вид
Почему Через редизайн .blue-btn становится зелёной. Имя врёт, но переименовать страшно: непонятно, где ещё используется.
Как надо Называй по роли: .btn--primary, .alert--error.
Двойная вложенность в элементе
Почему card__body__title привязывает элемент к структуре разметки. Переставишь заголовок на уровень выше — имя станет ложью.
Как надо Элемент принадлежит блоку, а не другому элементу: card__title.
Состояние оформлено как модификатор
Почему .modal--open выглядит как вариант модального окна, хотя это временное состояние. При чтении кода непонятно, кто и когда его ставит.
Как надо Состояния — через is- или data-state.
У компонента есть внешний margin
Почему В новом месте отступ оказывается лишним, и появляется card--flush, потом card--flush-top.
Как надо Отступы задаёт контейнер, а не компонент.
- Блок block
- Самостоятельная часть интерфейса, переносимая в другое место без изменений.
- Элемент element
- Часть блока, не имеющая смысла отдельно от него. Пишется через
__. - Модификатор modifier
- Постоянный вариант блока или элемента. Пишется через
--. - Состояние state
- Временное свойство: открыто, загружается, ошибка. Пишется через
is-или атрибутом. - Плоский селектор flat selector
- Селектор из одного класса. Легко перекрывается и не задевает чужие блоки.
Проверь себя
Имя должно называть роль. Цвет — свойство оформления, и он меняется чаще всего.
Элемент принадлежит блоку, а не другому элементу. Сколько бы уровней разметки ни было между ними.
Загрузка временна: началась и закончилась. Вариант оформления задаётся один раз при вёрстке.
Один класс вместо цепочки — правило легко перекрыть и невозможно случайно задеть чужой блок. Кода при этом обычно больше.
Расстояния — забота контейнера. Иначе один компонент требует правок под каждое место размещения.
Задание
Переименовать компонент
Уведомление свёрстано с именами про внешний вид, вложенным элементом и состоянием, притворяющимся модификатором. Плюс у блока внешний отступ.
Переименуй классы по БЭМ: блок alert, элементы через __, вариант через --, состояние через is-. Убери внешний margin у блока — отступ должен задавать контейнер .stack.
Подсказка 1
Блок — alert. Элементы принадлежат блоку напрямую: alert__text, alert__btn. Никакого __body__ посередине.
Подсказка 2
Состояние: <div class="alert is-muted"> и правило .alert.is-muted { opacity: .5; }
Подсказка 3
Убери margin-bottom у блока и добавь .stack { display: grid; gap: 16px; }
Показать решение
<div class="stack"> <div class="alert is-muted"> <p class="alert__text">Заказ не прошёл</p> <button class="alert__btn">Повторить</button> </div></div>.stack { display: grid; gap: 16px;}.alert { padding: 14px; background: #fdecee; border-left: 3px solid #c8394a; border-radius: 10px;}.alert__text { margin: 0 0 10px; color: #c8394a; }.alert__btn { padding: 8px 14px; font: inherit; color: #fff; background: #c8394a; border: 0; border-radius: 999px;}.alert.is-muted { opacity: .5; }