HTML по БЭМ
В БЭМ HTML-разметку можно создавать вручную или генерировать автоматически. Принципы организации HTML-кода в обоих случаях одинаковы:
Привязка блоков к DOM-узлу
Разметка страницы описывается в терминах блоков, элементов и модификаторов.
Чтобы указать, что блок, элемент или модификатор находятся на DOM-узле, их имена записываются в атрибуте class.
В простейшем случае одному DOM-узлу соответствует один блок:
<span class="menu"></span>
Несколько блоков на одном DOM-узле
Чтобы совместить стили и поведение нескольких БЭМ-сущностей, необходимо разместить их на одном DOM-узле. Для этого в значении атрибута class указываются имена БЭМ-сущностей, разделенные пробелом. Такой подход называется миксом.
Микс используется, например, чтобы добавить блоку или элементу модификатор. В примере ниже к стилям блока menu добавлены новые стили модификатора этого блока menu_theme_bright:
<span class="menu menu_theme_bright"></span>
Один блок на нескольких DOM-узлах
Для решения JavaScript-задач, например, для одновременной инициализации одного экземпляра блока в разных частях страницы, одну БЭМ-сущность можно разместить на нескольких DOM-узлах.
Пример включает особенности реализации фреймворка i-bem.js. Читать подробнее про i-bem.js
Вложенность элементов
Правила именования запрещают отражать иерархию в названии элемента (block__elem1__elem2). Но в HTML элементы можно вкладывать друг в друга. Допустима любая вложенность элементов.
В примере ниже пункты меню представлены ссылками. Такая структура блока реализуется за счет вложенности элементов:
<ul class="menu">
    <li class="menu__item">
        <a class="menu__link" href="https://">...</a>
    </li>
</ul>
Использование HTML-оберток
Чтобы расположить один блок относительно другого или позиционировать блоки внутри другого блока в БЭМ принято использовать миксы. Если решить эти задачи с помощью миксов невозможно, применяются HTML-обертки.
Расположение блока относительно других блоков
Чтобы позиционировать один блок относительно другого блока, используется микс.
В примере блоки header и footer позиционируются на странице с помощью микса с элементами блока page, которым заданы нужные стили. Элементы page__header и page__footer опциональные и применяются к блоку page, если необходимо разместить шапку (header) или подвал (footer) на странице. Блоки page, header и footer остаются независимыми, так как не содержат стили про взаимное позиционирование.
HTML-реализация:
<body class="page">
    <!-- верхний колонтитул и навигация -->
    <header class="header page__header">...</header>
    <!-- нижний колонтитул -->
    <footer class="footer page__footer">...</footer>
</body>
CSS-реализация:
.page__header {
    padding: 20px;
}
.page__footer {
    padding: 50px;
}
Расположение HTML-элементов внутри блока
Чтобы позиционировать HTML-элементы внутри блока, используется дополнительный элемент этого блока (например, button__inner). Элемент button__inner содержит стили про позиционирование внутри блока button и заменяет абстрактную обертку.
В примере иконка (блок icon) позиционируется внутри универсальной кнопки с помощью стилей элемента button__inner.
HTML-реализация:
<button class="button">
    <span class="button__inner">
        <span class="icon"></span>
    </span>
</button>
CSS-реализация:
.button__inner {
    margin: auto;
    width: 10px;
}
Создание HTML вручную
Чтобы создавать HTML вручную, необходимо следовать правилам, описанным выше.
В HTML разметка блока повторяется каждый раз, когда блок встречается на странице. Если разработчик пишет HTML вручную, исправлять ошибку или вносить дополнительные изменения необходимо в каждом экземпляре блока в разметке. Поэтому в БЭМ-проектах не принято писать HTML руками.
Автоматическая генерация HTML
Чтобы генерировать HTML-код автоматически и иметь возможность внести изменения в реализацию блока в одном файле и применить ко всем экземплярам блока в разметке, в БЭМ используются шаблоны.
Шаблоны — это технология реализации блока, результатом работы которой является HTML этого блока. С помощью шаблонов текущий HTML-элемент может быть подменен на другой или дополнен новыми.
В БЭМ-платформе разработана технология для создания шаблонов — BEMHTML. Все примеры в этом разделе приведены с использованием этого шаблонизатора.
Шаблоны в БЭМ пишутся декларативно. Это позволяет применять к ним основные принципы методологии:
Одни термины во всех технологиях
Шаблоны описываются в терминах блоков, элементов и модификаторов. Поэтому для работы с шаблонами используется дополнительный уровень абстракции над DOM-деревом — БЭМ-дерево. БЭМ-дерево описывает имена БЭМ-сущностей, их состояния, порядок и вложенность. На основании этих данных шаблонизатор строит дерево узлов HTML-разметки блока.
БЭМ-дерево можно выразить любым форматом, который поддерживает древовидную структуру.
Пример DOM-дерева и соответствующего ему БЭМ-дерева:
<header class="header">
    <img class="logo">
    <form class="search-form">
        <input class="input">
        <button class="button"></button>
    </form>
    <ul class="lang-switcher">
        <li class="lang-switcher__item">
            <a class="lang-switcher__link" href="url">en</a>
        </li>
        <li class="lang-switcher__item">
            <a class="lang-switcher__link" href="url">ru</a>
        </li>
    </ul>
</header>
БЭМ-дерево:
header
    logo
    search-form
        input
        button
    lang-switcher
        lang-switcher__item
            lang-switcher__link
        lang-switcher__item
            lang-switcher__link
Разделение кода на части
Код шаблонов хранится в отдельных файлах блока в соответствии с принципами организации файловой структуры проекта.
Шаблоны можно писать для всего блока и отдельно для элементов или модификаторов.
Пример файловой структуры блока menu:
menu/
    __item/
        menu__item.css
        menu__item.js
        menu__item.tmpl     # Шаблон элемента menu__item
    menu.css
    menu.js
    menu.tmpl               # Шаблон блока menu
Шаблон блока menu:
block('menu')(
  tag()('ul')               // Установлен тег <ul> для блока `menu`
);
Шаблон  элемента menu__item:
block('menu').elem('item')(
  tag()('li')               // Установлен тег <li> для всех элементов `menu__item`
);
HTML-реализация блока menu, результат работы шаблонов:
<ul class="menu">
  <li class="menu__item">...</li>
  <li class="menu__item">...</li>
</ul>
Использование уровней переопределения
С помощью уровней переопределения можно:
Переопределение шаблона
В примере рассмотрен шаблон блока menu из библиотеки, подключенной в проект на отдельный уровень переопределения:
project
    library.blocks/                 # Уровень переопределения с блоками библиотеки
        menu/                       # Блок menu из библиотеки
            __item/
                menu__item.tmpl     # Шаблон элемента menu__item
            menu.css
            menu.js
            menu.tmpl               # Шаблон блока menu
    common.blocks/
Шаблоны блока menu и элемента menu__item из библиотеки:
// Шаблон блока menu
block('menu')(
    tag()('ul'),
    attrs()(function() { ... }),
    addJs()(true),
    addMix()({ ... })
);
// Шаблон элемента menu__item
block('menu').elem('item')(
  tag()('li')
);
Блок menu в проекте представлен таким БЭМ-деревом:
menu
    menu__item
    menu__item
По БЭМ-дереву шаблонизатор по умолчанию формирует HTML-разметку в виде списка ul + li:
<ul class="menu">
  <li class="menu__item"><li>
  <li class="menu__item"><li>
</ul>
Чтобы переопределить шаблон и изменить связку ul + li на nav + a, необходимо:
- создать файлы шаблонов на уровне проекта; 
- задать новое значение свойству - tag.
Файловая структура проекта:
project
    library.blocks/                 # Уровень переопределения с блоками библиотеки
        menu/                       # Блок menu из библиотеки
            __item/
                menu__item.tmpl
            menu.css
            menu.js
            menu.tmpl
    common.blocks/                  # Уровень переопределения с блоками проекта
        menu/
            __item/
                menu__item.tmpl     # Переопределенный шаблон элемента menu__item
            menu.tmpl               # Переопределенный шаблон блока menu
Шаблоны с уровня common.blocks:
// Шаблон блока menu
block('menu')(
    // Переопределено только значения свойства tag
    tag()('nav')
);
// Шаблон элемента menu__item
block('menu').elem('item')(
  tag()('a')
);
В результате сборки изменения, внесенные в шаблон, применятся ко всем блокам menu и элементам menu__item в проекте:
// Шаблон блока menu с уровня библиотеки
block('menu')(
    tag()('ul'),
    attrs()(function() { ... }),
    addJs()(true),
    addMix()({ ... })
);
// Переопределенный шаблон блока menu
block('menu')(
    // Переопределено только значение свойства tag
    tag()('nav')
);
// Шаблон элемента menu__item с уровня библиотеки
block('menu').elem('item')(
  tag()('li')
);
// Переопределенный шаблон элемента menu__item
block('menu').elem('item')(
  tag()('a')
);
По аналогии с CSS нижнее правило переопределит верхнее. В результате применения шаблона HTML-код изменится на:
<nav class="menu">
  <a class="menu__item" href="...">...</a>
  <a class="menu__item" href="...">...</a>
</nav>
Добавление дополнительных HTML-элементов
С помощью шаблонов блоки можно изменять в runtime, например, добавлять новые HTML-элементы.
В проекте блок menu представлен таким БЭМ-деревом:
menu
    menu__item
    menu__item
Чтобы позиционировать элементы (menu__item) внутри блока (menu), необходимо создать служебный элемент, например, menu__inner. Новый элемент не относится к данным блока и служит только для добавления разметки. Поэтому его можно добавить в runtime, то есть при рендеринге блока в HTML.
В примере элемент menu__inner добавлен в шаблон с помощью JavaScript-кода:
block('menu')(
    tag()('menu'),
    // Добавлен элемент menu__inner
    content()(function() {
        return {
            elem: 'inner',
            content: this.ctx.content
        };
    })
);
// Установлен тег <ul> для элемента menu__inner
elem('inner')(
    tag()('ul')
);
В результате выполнения шаблона элемент menu__inner будет добавлен как отдельный узел в HTML-дерево:
<menu class="menu">
    <ul class="menu__inner">           // добавлен новый элемент menu__inner
      <li class="menu__item"></li>
      <li class="menu__item"></li>
    </ul>
</menu>