БЭМ

БЭМ (Блок, Элемент, Модификатор) — методология именования структурных элементов интерфейса, придуманная Яндексом. БЭМ позволяет команде лучше взаимодействовать, это понятный язык, на котором говорят верстальщики. Рутинные задачи (придумать названия классов, организовать стили) решаются сами собой. Разработчику, который пришел на любой проект, проще понять код. Не нужно подстраиваться под каждый проект, нужно только один раз разобраться с БЭМ-нотацией.

Общие сведения

Далее перечислены некоторые особенности работы с БЭМ, которые использует автор. Примеры кода приведены в LESS.

БЭМ в препроцессорах

БЭМ удобно использовать с препроцессорами. Используя вложенность селекторов и отсылку к родительскому селектору через & код получается простой и понятный:

.menu {
    &__item {} // -> .menu__item
    &_bottom {} // -> .menu__bottom
    &_bottom &__item {} // -> .menu__bottom .menu__item
}

В CSS получим:

.menu {}
.menu__item {}
.menu_bottom {}
.menu_bottom .menu__item {}

Именование в препроцессорах

При использовании родительского селектора & может показаться, что нужно добавлять его везде, где только можно. Однако, все не так просто и нужно учитывать ряд нюансов.

Например, нужно написать такую структуру CSS:

/* CSS, который должен получиться в итоге */
.checkout {}
.checkout__actions-price {}
.checkout__actions-totals {}
.checkout-cart {}

В этом коде есть:

  • два блока: checkout, checkout-cart;
  • и два элемента, которые принадлежат блоку checkout: checkout__actions-price, checkout__actions-totals.

Важно иметь ввиду, что имя блока, элемента или модификатора может состоять из нескольких слов:

  • checkout-cart
  • actions-price
  • actions-totals

И эти словосочетания не должны разделяться в коде. Есть искушение написать вот так, потому что слова не повторяются:

// Плохой вариант, так делать нельзя:
.checkout {
    &__actions {
        &-price {}
        &-totals {}
    }

    &-cart {}
}

Недостатки:

  • Смешались два блока: checkout и checkout-cart. Блок checkout-cart в коде запихан в скобки, которые принадлежат блоку checkout. Да, у них есть одно общее слово в имени, но эти блоки — разные и должны быть независимы.
  • Таким же образом смешались два элемента: actions-price и actions-totals. Это разные элементы, просто в их названии есть общее слово.
  • В коде получилось три уровня вложенности. Вложенность ухудшает читаемость. Чем меньше уровень вложенности, тем читабельнее код.
  • В процессе поддержки, когда нужно будет среди сотен файлов найти стили для элемента actions-price мы не найдем ничего. Потому что у нас в коде есть actions, есть price, но нет actions-price. Поиски затянутся, время работы увеличится, настроение разработчика ухудшится.

Названия блоков, элементов и модификаторов нельзя разделять. При именовании через & нужно исходить не из отдельных слов, а из названий сущностей. Правильный вариант будет таким:

// Правильный вариант:
.checkout {
    &__actions-price {}
    &__actions-totals {}
}

.checkout-cart {}

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

  • Меньше строк.
  • Меньше вложенности.
  • Лучше читаемость. Разработчик видит название сущностей целиком, проще «парсить» код глазами.
  • Имена БЭМ-сущностей не разделены, их поиск в проекте идет как надо.

Итоговое правило

& нельзя использовать для разделения имен сущностей. & используется только для отделения блоков, эелментов и модификаторов друг-от-друга. То есть сочетания &__ или &_ допустимы, а сочетание &- — запрещено.

Каскад

HTML и CSS изначально придуманы для оформления текстов. Они не предназначены для реализации сложных интерфейсов.

Для оформления текстов каскад вполне хорош, но нам надо делать сложные дизайны, динамические элементы, повторяющиеся в разных местах блоки и для этого нужно что-то более изящное, чем каскад.

БЭМ помогает создать плоскую структуру стилей и сделать блоки уникальными без каскада. Каскад в БЭМе становится помощником, а не догмой и не связывает руки.

Часто каскад используется для элементов блока с модификатором:

.menu {
    &__item {
        font-size: 14px;
        color: red;
    }

    &_bottom &__item { // модифицируем пункты меню внутри .menu_bottom
        font-size: 12px;
        color: blue;
    }
}

Каскад для блоков так же может быть использован, если нужно специально сделать блок зависимым от контекста:

.menu {
    // общие стили
}

.header {
    .menu {
        // только для хедера
    }
}

.footer {
    .menu {
        // только для футера
    }
}

Еще случай каскада для блоков. Например, есть блок .pagination. Он встречается на странице Категории, Товара, FAQ и вообще в любом разделе, где есть разбивка по страницам. Вид у блока одинаковый, но есть нюансы в отступах, положению и пр. Мы можем сделать так:

/* pagination.less */
.pagination {
    /* Общие стили блока, которые присутствуют на всех страницах */
}

/* category.less
Структура html:
<div class="category__pagination">
    <div class="pagination"></div>
</div> */
.category {
    &__pagination {
        /* задаем отступы, позиционируем, может что-то еще делаем */
    }

    .pagination {
        /* Тут можем, например, перекрасить что-то.
           Не обязательно добавлять модификатор к блоку .pagination_category. */
    }
}

Блок (независимый блок)

Независимый блок или просто блок, это самодостаточный элемент страницы, который при перемещении в другое место на странице или на другую страницу не теряет своей самодостаточности (выглядит так же, работает так же). Харисов: https://ru.bem.info/forum/-43/

  • Используем только классы, нельзя именовать блоки через id: .menu, .page-footer и т. д.
  • Если класс состоит из нескольких слов, то их нужно разделять -: .page-footer.
  • В БЭМ для элементов могут использоваться префиксы: l-, g-, b- и пр. Нам они особо не нужны. Незачем вводить сущности там, где можно без них обойтись. Используем только префикс .js- для навешивания обработчиков. Для таких классов нельзя писать стили, они используются только в JS.
  • Именовать js- блоки нужно так же, как обычные классы — через -, не камелкейсом: .js-btn-checkout. Камелкейс только для айдишников.
  • TODO: js- навешивать на все задействованные элементы или только на те, на которые непосредственно навешаны обработчики? https://gist.github.com/amiskov/c35a5d4d91e0108f076c0b359a189f48
  • Нет классов вне блоков. Все стили написаны только в контексте блоков. История создания БЭМ: https://ru.bem.info/forum/-147/

Имена блоков

Имена блоков должны отображать сущность блока, его смысл, а не его внешний вид:

<!-- Не правильно: -->
<input class="big_red_button">

<!-- Правильно: -->
<input class="order-button checkout__submit">

Однако, иногда попадаются дизайны, где сложно придумать имена классов исходя из логики поведения элементов. В таком случае можно задать модификаторы и по внешнему виду: .btn_small, .btn_red. Но только в том случае, когда реально не подойдет, например, .btn_secondary или .btn_primary.

Не нужно бояться создавать новые блоки. Если возникает желание сделать элемент у элемента (что не правильно), то, возможно, стоит ввести новый блок.

Префиксы

Префиксы добавляют сложности, повышают порог вхождения. Мы не используем префиксы, кроме .js-. Ништяки от их использования не перекрывают их сложности в наших реалиях. Без них мы ничего не теряем, зато новеньким проще врубиться в БЭМ.

Примеры префиксов, которые используют некоторые диалекты БЭМ:

  • .l- — префикс для лайаутных блоков. Тех, которые отвечают чисто за позиционирование, ширину, без визуального представления.
  • .b- — основной префикс.
  • .h- — внешняя обертка блока.
  • .js- — префикс для блоков, на которые навешивается JS-обработчики.
<div class="l-header"><!-- Позиционирование, ширина, высота -->
    <div class="h-header"><!-- Обертка блока, может задавать тень, фон, отступы и пр. -->
        <header class="b-header"></header><!-- оформление -->
    </div>
</div>

Перенос и реализация

Мадженто генерирует статические страницы. Верстка для них вставляется в темплейты руками, если есть заверстанные макеты.

Стили и скрипты для компонента (блок, который будет встречаться на разных страницах) нужно класть в отдельную папку:

src/
    modules/
        related-products/
            index.js
            index.less

Далее этот блок можно импортить на разных страницах. Например, блок related-products может быть показан на странице категории и на корзине:

src/
    modules/
        _category/
            index.js
            index.less
        _cart/
            index.js
            index.less
        related-products
            index.js
            index.less    

Верстку вставляем в .phtml-файлы вручную, стили и скрипты блока импортим так:

//_category/index.js
require('../related-products');

//_cart/index.js
require('../related-products');

Некоторые блоки (попапы, например) можно подключать через require.ensure(), если используется Вебпак или асинхронный require, если используется AMD.

И блок и элемент

Блок может быть элементом другого блока. В БЭМ это называется миксование.

Например, пост в блоге:

<section class="blog">
    <article class="blog__item post"></article> 
</section>

Зачем так делать? Для .post мы задаем стили такие, что позволяют его переносить с места на место. Для .blog__item мы задаем стили такие, которые форматируют статью так, как это нужно внутри блока .blog. Таким образом .post независим и в то же время как-то изменяется в контексте положения внутри .blog.

Возможный косяк: стили могут конфликтовать. Что-то внутри .post может перетереть стили для .blog__item. Выход: можно делать так (это решение более стабильное):

<section class="blog">
    <div class="blog__item">
        <article class="post"></article>
    </div>
</section>

Можно так же добавить модификатор к .post:

<section class="blog">
    <article class="post post_blog"></article>
</section>

Однако, решение с отдельным элементом .blog__item, оборачивающим .post более изящное и логически правильное

Элемент

Элемент — часть блока, отвечающая за какой-то его функционал.

Элемент не может существовать вне блока. Вне блока он теряет смысл. Например, кнопка Найти в форме поиска. Или ссылка в меню.

Именование

В имени элемента должно быть название блока и название элемента. В HTML элементы могут быть вложены друг-в-друга:

<ul class="menu">
    <li class="menu__item">
        <a class="menu__link" href="#">
            <span class="menu__text">О компании</span>
            <!-- Как вариант, можно написать menu__link-text -->
        </a>
    </li>
</ul>

Элемент menu__link принадлежит блоку menu, об этом нам сообщает его класс.

Не верно писать menu__item__link, потому что ссылка принадлежит меню, а не элементу menu__item. Элемент не может принадлежать другому элементу. Элементы — это атомарные, неделимые части блока. Нам не нужно в БЭМ отражать вложенность тэгов в HTML.

Если вам кажется, что нужно сделать элемент у элемента, значит нужно либо создать новый блок, либо сделать ваше БЭМ-дерево с одинарной вложенностью элементов. Не нужно бояться создавать новые блоки.

В LESS такая структура выглядит так:

.menu {
    &__item { }
    &__link { }
    &__text { }
}

Плоская структура, без каскада. Все понятно, легко поддерживать.

Еще пример правильного именования классов:

<div class="block">
    <div class="block__elem1">
        <div class="block__elem2"></div>
    </div>
    <div class="block__elem3"></div>
</div>
.block {}
.block__elem1 {}
.block__elem2 {}
.block__elem3 {}

@media queries

Стили для мобильных пишутся внутри блоков и элементов. Код должен быть написан так, чтобы мы могли скопировать CSS-код блока (.menu и то, что внутри него), вставить его в другой проект и все заработает. Не нужно их разносить по разным местам.

Так делать не правильно:

// Плохо
.menu {
    &__item { }
}

@media @phone {
    .menu {
        /* ... */
        &__item {
            /* ... */
        }
    }
}

Разнося стили для одного блока по разным местам мы нарушаем главный принцип хорошей разработки: Don't repeat yourself. Это ухудшает поддерживаемость кода. Вася напишет стили для разных медиа, а Петя это может и не заметить.

Медиа-запросы, написанные внутри блоков и элементов обеспечат поддерживаемый, не повторяющийся код:

// Хорошо
.menu {
    font-size: 14px;
    // Все стили для блока в одном месте
    @mobile {
        width: auto;
        margin: 0 5%;
    }

    @media @large {
        width: 1200px;
        margin: 0 auto;
    }

    &__item {
        // Все стили для элемента в одном месте
        font-size: 14px;

        @media @large {
            font-size: 18px;
        }
    }
}

Модификаторы

Изменить блок можно добавив к нему модификатор. Элементы блока могут быть переопределены по каскаду от модификатора, это нормально:

.menu {
    &__item {
        font-size: 14px;
        color: red;
    }

    &_bottom &__item {
        font-size: 12px;
        color: blue;
    }
}

Изменить элемент так же можно через модификатор. Например, активный пункт меню:

<li class="menu__item menu__item_active"></li>

Нужно быть внимательным. Если нужно сделать активной ссылку — подойдет модификатор. Если нужно сильно переделать элемент, то может быть лучше ввести новый блок. Не нужно каскадом переписывать все правила.

Модификаторы состояния блока/элемента, которые задействованы в JS нужно использовать без названия блока/элемента (стиль no-namespace):

<li class="menu__item _active"></li>

При этом важно, чтобы стили не были заданы без отрыва от блока. Модификатор без блока существовать не может:

.menu {
    &__item {
        background: gray;
        color: black;

        &._active {
            background: black;
            color: white;
        }
    }
}

Такой подход позволяет:

  • проще добавлять/удалять классы в JS
  • отделить JS от HTML: при изменении имени блока в HTML не нужно будет менять JS

Не допускается писать стили для _active вне контекста модифицируемого блока или элемента. В БЭМ не бывает глобальных модификаторов

// Плохо
._active { // Такой класс повлияет на все блоки/элементы, к которым он добавится
    font-weight: bold;
}

// Хорошо
.menu {
    &__link {
        &._active { // модификатор четко для ссылки меню, ничего другого он не изменит
            background: red;
        }
    }
}

Если нужно сделать класс, который будет иметь постоянный набор стилей, которые нужно применять к разным блокам/элементам, то будет просто блок, который можно примиксовывать куда нужно. Например, .hidden.

Когда стоит применять модификаторы без указания блока/элемента

Если мы модифицируем состояние элемента в JS (активная ссылка, текущий пункт меню, текущий таб и пр.), то допускается применить модификатор без указания неймспейса: _active.

Если же нужно модифицировать вид блока (форма поиска полная и сокращенная, меню в хедере и футере), то нужен модификатор с полным именем: .menu_top

<div class="menu menu_top"></div>
<div class="menu menu_bottom"></div>

<div class="search search_mini"></div>
<div class="search search_full"></div>

Тип модификатора

В оригинальном БЭМ принято указывать, что именно меняет модификатор: &_size_big, &_color_red, &_type_warning и пр.

Мы опускаем тип модификатора: &_big, &_red, &_warning. Этого хватает, так проще.

Текст из админки

Для текста, который вводит пользователь, нормально делать каскад по HTML-элементам:

.cms-text {
    h2 {}
    p {}
    /* ... */
}