Методология 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) — он дополняет, а не заменяет.