Методология BEM: именование классов

Учимся называть классы так, чтобы по имени было понятно, что это, к чему относится и в каком оно состоянии — без вложенных селекторов и войн специфичности.

BEM (Block — Element — Modifier) — это соглашение об именовании классов, при котором каждый класс самодостаточен и описывает компонент (блок), его часть (элемент) или вариацию (модификатор).

В прошлом уроке мы видели, как длинные вложенные селекторы раздувают специфичность и превращают правки в борьбу. BEM решает проблему на уровне именования: вместо .card .header .title вы пишете один плоский класс .card__title. Специфичность остаётся низкой и одинаковой почти у всех правил — а значит, каскад предсказуем.

Зачем это на практике

Главная боль командного CSS — конфликты имён и непонятная связь стилей с разметкой. Класс .title может быть и у карточки, и у модалки, и у сайдбара; правишь один — ломается другой. BEM делает имена уникальными по смыслу и читаемыми: глядя на .modal__close--disabled, любой в команде понимает «это кнопка закрытия модалки в выключенном состоянии», даже не открывая CSS. Имя становится документацией.

Три кирпича: Block, Element, Modifier

Block — самостоятельный компонент

Блок — это переиспользуемый, независимый кусок интерфейса: card, menu, search-form, button. Имя блока — просто его название: .card. Блок ничего не знает о своём окружении и может встать в любое место страницы.

Element — часть блока

Элемент — кусок блока, который вне него не имеет смысла: заголовок карточки, пункт меню. Записывается через два подчёркивания: .card__title, .menu__item. Имя всегда начинается с имени блока — это и есть «привязка без вложенности».

Modifier — вариация или состояние

Модификатор меняет внешний вид или поведение блока/элемента: размер, тему, состояние. Записывается через два дефиса: .button--primary, .card--featured, .menu__item--active. Модификатор не заменяет базовый класс, а дополняет его.

<article class="card card--featured">
  <h3 class="card__title">Тариф Pro</h3>
  <p class="card__text">Всё включено.</p>
  <a class="card__button card__button--wide" href="#">Купить</a>
</article>

А CSS остаётся плоским — у каждого правила специфичность ровно (0,1,0):

.card { border: 1px solid #ddd; border-radius: 8px; }
.card--featured { border-color: gold; }
.card__title { font-size: 1.25rem; }
.card__button { display: inline-block; padding: 8px 16px; }
.card__button--wide { width: 100%; }

Зачем плоская низкая специфичность

Заметьте: в примере выше нет ни одного вложенного селектора. Все классы — один уровень, специфичность у всех одинаковая и минимальная. Это даёт два важных свойства. Во-первых, любое правило легко переопределить другим правилом такой же специфичности — достаточно объявить его ниже или навесить модификатор. Во-вторых, порядок в разметке не диктует стили: .card__title выглядит одинаково, где бы внутри карточки он ни лежал. Это полная противоположность подходу «через структуру», где перенос элемента глубже ломает стиль.

Переиспользуемые компоненты

BEM-блок — это готовый компонент, который не зависит от места. Один и тот же .card можно положить в сетку каталога, в сайдбар или в модалку — он не сломается, потому что не цепляется за родителей. Если в каком-то месте нужен другой вид, вы не лезете в чужой CSS, а добавляете модификатор:

<!-- та же карточка, но компактная в сайдбаре -->
<article class="card card--compact"> ... </article>
.card--compact { padding: 8px; font-size: 0.875rem; }

Отступы между компонентами при этом задаёт не сам блок, а внешний контекст (родительская сетка). Блок отвечает только за себя — это и делает его по-настоящему переносимым.

Как это работает «под капотом» (а точнее — почему вообще работает)

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

Частые ошибки

  • Цепочки элементов block__el1__el2. Двойного вложения в BEM не бывает: элемент всегда привязан к блоку, а не к другому элементу. Не .card__body__title, а просто .card__title. Вложенность в DOM может быть любой — имя плоское.
  • Модификатор без базового класса. class="card--featured" без card не получит базовых стилей карточки. Модификатор дополняет, а не заменяет: пишут оба — class="card card--featured".
  • Лишний блок ради состояния. «Активный пункт» — это не новый блок, а модификатор: .menu__item--active, а не отдельный класс .active-item.
  • Возврат к вложенным селекторам «для краткости». .card .card__title вместо .card__title сводит на нет всю выгоду BEM — специфичность снова растёт. Класс уже уникален, дополнительная привязка не нужна.
  • Имена блоков из одного слова на грани коллизии. .item, .box, .wrap слишком общие и рано или поздно столкнутся. Блоку давайте осмысленное имя: .product-card, .search-box.

Итоги

  • BEM — соглашение об именовании: block, block__element (два подчёркивания), block--modifier (два дефиса).
  • Блок — независимый переиспользуемый компонент; элемент — его часть, бессмысленная отдельно; модификатор — вариация или состояние.
  • Все классы плоские и одной низкой специфичности — каскад предсказуем, любое правило легко переопределить.
  • Имя элемента всегда начинается с имени блока, но НЕ образует цепочек: .card__title, а не .card__body__title.
  • Модификатор пишут вместе с базовым классом (card card--featured) — он дополняет, а не заменяет.
Проверьте себя
1. Как по BEM правильно назвать класс заголовка внутри блока card?
A.card-title
B.card__title
C.card--title
D.card .title
2. Что означает запись .menu__item--active?
AОтдельный независимый блок active
BЭлемент item блока menu в состоянии-модификаторе active
CВложенный элемент item внутри элемента active
DБлок menu с двумя элементами item и active
3. Почему BEM держит специфичность селекторов плоской и низкой?
AЧтобы CSS-файл занимал меньше места на диске
BЧтобы любое правило можно было легко переопределить другим правилом такой же специфичности, без войн и !important
CПотому что браузеры не поддерживают вложенные селекторы
DЧтобы стили применялись быстрее за счёт меньшего числа классов