БЭМ (BEM, в поиске набирают и «бем») — методология именования CSS-классов, выросшая из вёрстки Яндекса: интерфейс делят на независимые блоки, имя элемента отделяют от имени блока двумя подчёркиваниями (menu__item), имя модификатора — одним (menu_hidden). SCSS добавляет вложенность, переменные и миксины, поэтому длинные БЭМ-имена собираются из амперсанда и суффикса вместо ручного набора.
Что такое BEM?
BEM (Block, Element, Modifier) — это методология именования CSS-классов, которая позволяет создавать читаемый и поддерживаемый код. Официальная документация методологии определяет три сущности так:
- Блок (Block) — «логически и функционально независимый компонент страницы, аналог компонента в Web Components».
- Элемент (Element) — «составная часть блока, которая не может использоваться в отрыве от него. Элементы не существуют вне блока».
- Модификатор (Modifier) — «БЭМ-сущность, определяющая внешний вид, состояние и поведение блока или элемента».
Методология выросла из практики большого портала. По документации Яндекса, «история БЭМ началась в 2005 году», в 2006-м правила обкатали на Яндекс.Музыке и Я.ру, к осени 2007 года вёрстка устоялась, а «в марте 2009 года вышла версия Лего 2.0. Этим событием оканчивается вёрстка независимыми блоками и начинается БЭМ». В 2010 году исходники ушли в open source (bem.info).
Практический смысл дают два правила, которые методология выдерживает жёстко. Элемент принадлежит ровно одному блоку: «каждый элемент может принадлежать только одному блоку». Модификатор не живёт сам по себе: «модификатор не может использоваться в отрыве от модифицируемого блока или элемента» — базовый класс на узле остаётся, модификатор к нему добавляется.
Примеры именования

Рассмотрим примеры именования классов в BEM. В классической схеме, которую описывает документация методологии, полное имя строится по шаблону block-name__elem-name_mod-name_mod-val:
- Блок:
.button - Элемент:
.button__icon— «имя элемента отделяется от имени блока двумя подчеркиваниями (__)» - Модификатор:
.button_primary— «имя модификатора отделяется от имени блока или элемента одним подчеркиванием (_)» - Модификатор со значением:
.button_theme_islands— «значение модификатора отделяется от имени модификатора одним подчеркиванием (_)»
Написание .button--primary с двумя дефисами — тоже БЭМ, но другая схема. Документация перечисляет четыре альтернативы классической, и все они равноправны, пока проект придерживается одной. Ниже один и тот же случай — блок menu-list, элемент item, модификатор visible — записан по шаблону каждой схемы:
| Схема | Шаблон имени | Как выглядит класс |
|---|---|---|
| Классическая | block-name__elem-name_mod-name_mod-val |
menu-list__item_visible |
| Two Dashes | block-name__elem-name--mod-name_mod-val |
menu-list__item--visible |
| CamelCase | blockName__elemName_modName_modVal |
menuList__item_visible |
| React | BlockName-ElemName_modName_modVal |
MenuList-Item_visible |
| No-namespace | имя блока перед модификатором не указывается | _visible |
Разница между схемами — в читаемости и в совместимости с инструментами. У Two Dashes граница между элементом и модификатором видна с одного взгляда: разделители разные. Классическую схему документация описывает как основную, остальные четыре идут в ней разделом «альтернативные». Схему выбирают один раз на проект и закрепляют правилом линтера, иначе через полгода в кодовой базе окажутся обе.
Двойное подчёркивание как служебный разделитель встречается не только в CSS: в Python так помечают магические методы — например, методы __add__() и __radd__().
Четыре правила БЭМ, которые нарушают чаще всего
Разделители запоминаются за пять минут, а ломается методология на правилах, которых в шпаргалках обычно нет.
Элемента у элемента не бывает
Соблазн повторить в классе DOM-иерархию возникает у каждого: внутри search-form__content лежит поле, значит будет search-form__content__input. Документация закрывает этот путь: «элемент — всегда часть блока, а не другого элемента. Это означает, что в названии элементов нельзя прописывать иерархию вида block__elem1__elem2». Правильная запись плоская — search-form__input, search-form__button, — и она же спасает при переносе: вложенность в разметке меняется, имя класса остаётся.
Модификатор не ходит без базового класса
Модификатор описывает только отличие от базовой сущности, поэтому на узле остаются оба класса:
<!-- правильно --> <form class="search-form search-form_theme_islands"></form> <!-- неправильно: базового класса нет, стили блока не применятся --> <form class="search-form_theme_islands"></form>
Никаких селекторов по тегам и id
«В БЭМ не используют селекторы тегов и идентификаторов. Стили блоков и элементов описываются через селекторы классов», — и это не вкусовщина. Уточнение из той же документации объясняет цену: «объединение тега и класса (например, button.button) повышает специфичность CSS-правил, что усложняет задачу их переопределения». Класс с единичной специфичностью перебивается следующим классом, связка «тег + класс» требует уже более тяжёлого селектора, и дальше специфичность растёт лавиной.
Отступы блока задаёт родитель
Блок должен переноситься в любое место страницы без правок, поэтому «в CSS по БЭМ стили, отвечающие за внешнюю геометрию и позиционирование, задаются через родительский блок»:
.button {
font-family: Arial, sans-serif;
border: 1px solid black;
}
.header__button {
margin: 30px;
position: relative;
}
Приём, которым это делают, называется микс — «использование разных БЭМ-сущностей на одном DOM-узле». На узел вешают класс блока и класс элемента родителя: <div class="search-form header__search-form">. Первый отвечает за внешний вид, второй — за место на странице.
Что такое SCSS?
SCSS (Sassy CSS) — синтаксис препроцессора Sass, надстройка над CSS: любой валидный CSS-файл является валидным SCSS-файлом, а сверху добавлены переменные, вложенность, миксины, функции и модули. Для БЭМ важна одна конкретная возможность — родительский селектор &. Документация Sass формулирует правило так: пока внешний селектор заканчивается буквенно-цифровым именем (класс, идентификатор, тег), к родительскому селектору можно дописывать текст. Именно поэтому БЭМ-имена не приходится набирать целиком — официальный пример из документации собран как раз по методологии:
.accordion {
&__copy {
display: none;
&--open {
display: block;
}
}
}
На выходе получаются .accordion__copy и .accordion__copy--open — плоские классы единичной специфичности, как того требует БЭМ. У приёма есть ограничение, о которое спотыкаются при попытке приписать тег: «span& не допускается», потому что родительский селектор может быть заменён селектором типа и разрешён только там, где допустим сам селектор типа.
Второй эффект вложенности неочевиден и стоит дорого: полного имени .accordion__copy в исходниках нет — оно рождается только при компиляции. Поиск по проекту строкой accordion__copy не найдёт ничего. Поэтому вложенность держат неглубокой (блок → элемент → модификатор, дальше не спускаются), а в командах, где важен grep, элементы пишут полным именем без амперсанда.
Примеры использования
Пример использования переменных и вложенных селекторов в SCSS, записанный по классической схеме именования:
$primary-color: #3498db;
.button {
background-color: $primary-color;
&__icon {
color: white;
}
&_primary {
border: 1px solid $primary-color;
}
}
Такой подход упрощает изменение и поддержку стилей: цвет правится в одном месте, а структура файла повторяет структуру блока.
Совместное использование BEM и SCSS

Структурирование файлов
Раскладка по папкам /blocks, /elements, /modifiers выглядит логично и противоречит методологии: элемент и модификатор не существуют отдельно от блока, значит и лежать отдельно им негде. Документация задаёт другой принцип: «в БЭМ блок первичен, а технологии — вторичны. Поэтому код проекта хранится по-блочно: каждому блоку соответствует свой набор файлов, необходимых для его реализации». Правило «одна технология — один файл» дополняет: «имена файлов реализации совпадают с именем блока», а «код модификаторов и элементов также хранится в отдельных файлах блока».
Директории именуются теми же разделителями, что и классы: «имена директорий элементов начинаются с двойного подчеркивания (__). Имена директорий модификаторов начинаются с одинарного подчеркивания (_)». Пример структуры проекта в схеме Nested — все файлы блока button лежат в его директории:
styles/
blocks/
button/
_button.scss // блок
__icon/
_button__icon.scss // элемент
_theme/
_button_theme_islands.scss // модификатор со значением
card/
_card.scss
_index.scss
style.scss
Ведущее подчёркивание в именах файлов — требование Sass, а не БЭМ: файлы, начинающиеся с _, препроцессор считает партиалами и не компилирует в отдельный CSS. Само имя после подчёркивания остаётся БЭМ-именем блока.
Документация описывает три схемы организации файлов: Nested — полная иерархия директорий, Flat — «директории для блоков не используются», всё лежит одним уровнем, Flex — комбинация обеих. Flat берут для небольших проектов и прототипов, Nested — когда у блоков появляются элементы, модификаторы и своя логика. Переход между схемами делается один раз и целиком: смешанная раскладка ломает автоматический поиск файлов сборщиком.
Использование переменных и миксинов
SCSS позволяет использовать переменные и миксины для уменьшения дублирования кода. Пример на актуальном синтаксисе — с модулями вместо глобальных переменных:
// _variables.scss
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
// _mixins.scss
@mixin border-radius($radius) {
border-radius: $radius;
}
Вендорные префиксы -webkit-border-radius, -moz-border-radius и -ms-border-radius, которые кочуют по старым руководствам, в исходниках руками не пишут: префиксы расставляет постпроцессор на этапе сборки по списку поддерживаемых браузеров проекта.
Современный Sass: @use вместо @import
Главное изменение последних лет касается как раз того места, где БЭМ и SCSS сходятся, — сборки блоков в один файл. Официальная документация Sass фиксирует: начиная с Dart Sass 1.80.0 правило @import объявлено устаревшим и будет удалено из языка в Dart Sass 3.0.0, вместо него следует использовать @use (sass-lang.com). Компилятор на такой файл уже ругается предупреждением о deprecation.
Разница не косметическая. @import вываливал переменные и миксины в общее пространство имён и подключал один и тот же файл столько раз, сколько его импортировали. @use «загружает миксины, функции и переменные из других таблиц стилей Sass», а стили, загруженные таким образом, «попадают в скомпилированный CSS ровно один раз, сколько бы раз их ни загружали». Обращение идёт через пространство имён по имени файла, его можно переназначить через as, а члены, чьё имя начинается с - или _, остаются приватными и в публичный API модуля не входят.
Второе изменение бьёт по типовому БЭМ-коду с модификаторами. Функции darken(), lighten(), saturate(), desaturate(), adjust-hue(), opacify(), transparentize(), fade-in() и fade-out() объявлены устаревшими (предупреждения — с Dart Sass 1.79.0) и, по документации, «в Dart Sass 2.0.0 эти функции будут удалены полностью». Замена — color.adjust() и color.scale() из модуля sass:color, причём с явным указанием цветового пространства: вместо lighten($color, 15%) документация предписывает color.adjust($color, $lightness: 15%, $space: hsl).
Собранная по этим правилам точка входа выглядит так. Файл _index.scss в папке загружается автоматически при обращении к самой папке, а @forward «загружает таблицу стилей Sass и делает её миксины, функции и переменные доступными» тем, кто подключит модуль через @use:
// blocks/_index.scss
@forward 'button';
@forward 'card';
// style.scss
@use 'sass:color';
@use 'variables' as v;
@use 'blocks';
.button {
background-color: v.$primary-color;
&_primary {
background-color: color.adjust(v.$primary-color, $lightness: -10%, $space: hsl);
}
}
Одна оговорка про инструменты: @use поддерживает только Dart Sass, и появился он в версии 1.23.0. Проекты на LibSass и Ruby Sass это правило не понимают — там остаётся @import, и это отдельный аргумент за переезд на Dart Sass.
Преимущества инструментов
Преимущества BEM
- Читаемость и поддерживаемость кода. Имя класса сразу показывает структуру и взаимосвязи:
menu__item_visibleчитается как «элемент item блока menu в состоянии visible», и для этого не нужно открывать разметку. - Повторное использование. Блок не знает о своём окружении и не задаёт себе внешних отступов, поэтому переносится в другую часть проекта без правок стилей.
- Устойчивость к изменениям. Модификаторы меняют вид и состояние, не задевая другие части интерфейса: специфичность у всех селекторов одинаковая, каскад не воюет сам с собой.
- Общий словарь в команде. Спор «это блок или элемент» решается правилом «элементы не существуют вне блока», а не вкусом ведущего разработчика.
Плата за это — длинные имена классов и распухшая разметка: search-form__input search-form__input_size_m вместо короткого input. На проектах в один экран методология скорее мешает, её выгода начинается там, где над стилями работает больше одного человека или живут больше нескольких десятков компонентов.
Преимущества SCSS
- Модульность. Разделение кода на небольшие файлы блоков и сборка через
@useупрощают организацию проекта. - Переменные. Цвета, размеры, шрифты и брейкпоинты правятся в одном месте.
- Миксины и функции. Повторяющиеся куски описываются один раз и подключаются по имени.
- Родительский селектор. Механизм
&собирает БЭМ-имена из суффиксов, поэтому структура файла повторяет структуру блока.
Примеры кода
Создание кнопки
Файл блока целиком: переменные приходят из модуля, модификатор затемняет фон через color.adjust, элемент и модификатор собраны родительским селектором.
// blocks/button/_button.scss
@use 'sass:color';
@use '../../variables' as v;
$button-padding: 10px 20px;
.button {
padding: $button-padding;
background-color: v.$primary-color;
border: none;
cursor: pointer;
&__icon {
margin-right: 5px;
}
&_primary {
background-color: color.adjust(v.$primary-color, $lightness: -10%, $space: hsl);
}
}
Тот же блок в старой записи выглядел как @import плюс darken($primary-color, 10%). Оба вызова компилируются до сих пор, но оба помечены как устаревшие и в мажорных версиях Dart Sass исчезнут — переписывать их дешевле сейчас, по одному блоку, чем всем проектом в день, когда сборка встанет.
Интеграция в основной файл
Точка входа подключает модули папками, по одной строке на директорию:
// style.scss @use 'variables'; @use 'mixins'; @use 'blocks'; // подхватит blocks/_index.scss
Внутри blocks/_index.scss лежат строки @forward 'button/button'; для каждого блока. Добавление нового блока в проект — это новая директория и одна строка в индексе, остальные файлы не трогаются.
Как переехать на БЭМ в существующем проекте
Переписывать стили целиком не нужно и опасно. Рабочий порядок — по одному блоку за раз:
- Выбрать переиспользуемый компонент, который чаще всего ломается при правках, — кнопку, форму, карточку.
- Вынести его стили в отдельный файл-партиал с именем блока и заменить селекторы по тегам и id на классы.
- Перенести внешние отступы и позиционирование из блока в элемент родителя через микс.
- Закрепить выбранную схему именования правилом линтера, чтобы новый код не приезжал в старом стиле.
- Повторить со следующим блоком; старые стили остаются рядом, пока последний их потребитель не переписан.
Частые вопросы
Что такое БЭМ простыми словами?
БЭМ — соглашение о том, как называть CSS-классы, чтобы имя класса рассказывало о его роли. Интерфейс делится на блоки (независимые компоненты), у блока есть элементы (части, которые вне блока не существуют) и модификаторы (варианты вида, состояния и поведения). Имя элемента отделяется от имени блока двумя подчёркиваниями, имя модификатора — одним: menu, menu__item, menu__item_visible.
Чем отличается запись block__elem_mod от block__elem—mod?
Это две разные схемы именования одной методологии. Первая — классическая, её описывает официальная документация БЭМ: модификатор отделяется одним подчёркиванием. Вторая называется Two Dashes и отделяет модификатор двумя дефисами. Документация перечисляет также схемы CamelCase, React и no-namespace. Все они допустимы, выбрать нужно одну на проект.
Можно ли писать имя вида block__elem1__elem2?
Нет. Элемент всегда часть блока, а не другого элемента, поэтому иерархию вложенности в имя класса не переносят. Вместо search-form__content__input пишут плоское search-form__input. Это правило и делает разметку устойчивой: вложенность в HTML можно менять, имя класса от этого не меняется.
Как писать БЭМ-классы в SCSS?
Через родительский селектор: внутри правила блока пишут амперсанд с суффиксом элемента или модификатора, и препроцессор собирает полное имя при компиляции. Ограничение одно: у полного имени класса нет текстового вхождения в исходниках, поэтому поиск по проекту его не найдёт. Из-за этого вложенность держат неглубокой.
Чем заменить @import и darken в современном Sass?
Правило @import объявлено устаревшим начиная с Dart Sass 1.80.0 и будет удалено в Dart Sass 3.0.0, вместо него используют @use и @forward. Функция darken вместе с lighten, saturate, desaturate и другими из этого набора получила предупреждения об устаревании в Dart Sass 1.79.0 и будет удалена в Dart Sass 2.0.0; замена — color.adjust и color.scale из модуля sass:color.
Заключение
БЭМ даёт правила именования, SCSS — механику, которая делает эти правила недорогими в написании. Порядок внедрения простой: зафиксировать схему именования, разложить файлы по блокам, перевести сборку на @use и @forward, а цветовые функции — на модуль sass:color. Дальше методология работает сама: класс говорит, чей он и в каком состоянии, а блок переезжает между страницами без правок.
БЭМ описывает ручную вёрстку. Когда интерфейс собирается в конструкторе, именование берёт на себя платформа, и дисциплина переносится на другие сущности — например, на языковые версии страниц, как в локализации в Webflow.
