Содержание
  1. Что такое BEM?
  2. Четыре правила БЭМ, которые нарушают чаще всего
  3. Что такое SCSS?
  4. Совместное использование BEM и SCSS
  5. Современный Sass: @use вместо @import
  6. Преимущества инструментов
  7. Примеры кода
  8. Как переехать на БЭМ в существующем проекте
  9. Частые вопросы
  10. Заключение
ГайдыОбновлено · 08.2026

Руководство по BEM и SCSS: структурируем, чистим, мастерим

6 июня 2024 · 14 минут чтения

BEM + SCSS: чиним вложенный элемент и переводим сборку на @use1:58

БЭМ (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

Файлы блока button: схема Nested
Файлы блока button: схема Nested

Структурирование файлов

Раскладка по папкам /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

  1. Читаемость и поддерживаемость кода. Имя класса сразу показывает структуру и взаимосвязи: menu__item_visible читается как «элемент item блока menu в состоянии visible», и для этого не нужно открывать разметку.
  2. Повторное использование. Блок не знает о своём окружении и не задаёт себе внешних отступов, поэтому переносится в другую часть проекта без правок стилей.
  3. Устойчивость к изменениям. Модификаторы меняют вид и состояние, не задевая другие части интерфейса: специфичность у всех селекторов одинаковая, каскад не воюет сам с собой.
  4. Общий словарь в команде. Спор «это блок или элемент» решается правилом «элементы не существуют вне блока», а не вкусом ведущего разработчика.

Плата за это — длинные имена классов и распухшая разметка: search-form__input search-form__input_size_m вместо короткого input. На проектах в один экран методология скорее мешает, её выгода начинается там, где над стилями работает больше одного человека или живут больше нескольких десятков компонентов.

Преимущества SCSS

  1. Модульность. Разделение кода на небольшие файлы блоков и сборка через @use упрощают организацию проекта.
  2. Переменные. Цвета, размеры, шрифты и брейкпоинты правятся в одном месте.
  3. Миксины и функции. Повторяющиеся куски описываются один раз и подключаются по имени.
  4. Родительский селектор. Механизм & собирает БЭМ-имена из суффиксов, поэтому структура файла повторяет структуру блока.

Примеры кода

Создание кнопки

Файл блока целиком: переменные приходят из модуля, модификатор затемняет фон через 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'; для каждого блока. Добавление нового блока в проект — это новая директория и одна строка в индексе, остальные файлы не трогаются.

Как переехать на БЭМ в существующем проекте

Переписывать стили целиком не нужно и опасно. Рабочий порядок — по одному блоку за раз:

  1. Выбрать переиспользуемый компонент, который чаще всего ломается при правках, — кнопку, форму, карточку.
  2. Вынести его стили в отдельный файл-партиал с именем блока и заменить селекторы по тегам и id на классы.
  3. Перенести внешние отступы и позиционирование из блока в элемент родителя через микс.
  4. Закрепить выбранную схему именования правилом линтера, чтобы новый код не приезжал в старом стиле.
  5. Повторить со следующим блоком; старые стили остаются рядом, пока последний их потребитель не переписан.

Частые вопросы

Что такое БЭМ простыми словами?

БЭМ — соглашение о том, как называть 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.

Читайте также

3 материала