Skip to main content

Що таке методологія BEM (блок елемент модифікатор)

BEM (Block Element Modifier) - конвенція іменування CSS-класів, де кожен клас слідує шаблону block__element--modifier, щоб запобігти конфліктам стилів у великих проєктах.

Теорія

TL;DR

  • Block = самостійна UI-одиниця (button). Element = частина, що існує тільки всередині блоку (button__icon). Modifier = варіація або стан (button--primary).
  • Як LEGO: block це основна деталь, element кріпиться тільки до неї, modifier змінює її вигляд.
  • Головне: .menu__item--active стосується тільки меню і ніколи не зіткнеться з .button--active.
  • Використовуй BEM у команді від 5+ розробників або при CSS понад 10 тисяч рядків. Для маленьких прототипів і CSS-in-JS не потрібно.

Швидкий приклад

html
<button class="button button--primary"> <span class="button__icon"></span> Натисни </button>
css
.button { padding: 10px; border: 1px solid; } /* Block */ .button__icon { display: inline-block; margin-right: 5px; } /* Element */ .button--primary { background: blue; color: white; } /* Модифікатор блоку */ .button__icon--large { font-size: 20px; } /* Модифікатор елемента */

Назва класу сама описує структуру. button__icon--large - велика іконка всередині кнопки. Не більше.

Ключова різниця

Клас .active - глобальний. Злий три гілки коду і отримаєш три різні правила .active, що борються між собою. Це найпоширеніший CSS-конфлікт у командних проєктах. BEM дає простір імен для всього: .card--active, .menu__item--active, .button--active - три незалежних селектори, які не перетинаються.

Коли використовувати

  • Команда від 5+ людей або CSS понад 10k рядків: BEM-іменування запобігає конфліктам при злитті гілок.
  • Бібліотеки компонентів для повторного використання: елементи прив'язані до блоку, немає семантичного дрейфу з часом.
  • Vanilla CSS без білд-тулів: BEM дає ізоляцію без PostCSS або Webpack.
  • CSS-in-JS (Styled Components, Emotion): пропускай BEM, бібліотека сама хешує класи.
  • Tailwind CSS: пропускай BEM, утилітарні класи замінюють будь-які конвенції іменування.
  • Маленький соло-проєкт до 5 файлів: прості класи писати швидше, оверхед BEM не виправданий.

Порівняння

АспектBEMFlat CSS (.btn.active)CSS Modules
Іменуванняblock__element--mod.class, .stateButton_module__root
МасштабованістьВисока (простір імен)Низька (глобальні колізії)Висока (хешування при збірці)
Розмір команди5+ розробниківСоло або мала командаБудь-який
ДебагDevTools показує ієрархіюШукай глобальні стиліБезпечно, але назви нечитабельні
Найкраще дляVanilla або командний CSSПрототипиReact/Vue компоненти

Як браузер обробляє BEM

Жодної магії. Браузер читає BEM-класи як звичайні CSS-селектори. .button__element має специфічність (0,1,0), тобто рівно стільки ж, скільки й будь-який одиночний клас. Ось і все. Структура закодована в назві, а не в каскаді чи shadow DOM. CSS пишуть плоско, ніяких .button .button__icon {}.

Типові помилки

Вкладені селектори в CSS:

css
/* Неправильно - ламається без PostCSS, суперечить BEM */ .button { .icon { color: red; } } /* Правильно - завжди плоскі селектори */ .button__icon { color: red; }

Занадто довгий ланцюжок модифікаторів:

css
/* Неправильно - важко читати і перевизначити */ .button--primary--large--rounded { } /* Правильно - окремі класи, комбінуй в HTML */ .button--primary.button--large { }

Вкладені елементи:

css
/* Неправильно - BEM іде тільки на один рівень для елементів */ .header__nav__link { } /* Правильно - link це елемент nav */ .nav__link { }

Очікування, що модифікатор блоку вплине на модифікатор елемента:

css
.card { transition: opacity 0.3s; } .card--loading { opacity: 0.5; } /* Впливає на блок */ .card__spinner--fast { animation: spin 1s infinite; display: block; } /* Реальність: спінер з --fast крутиться незалежно від .card--loading. * Модифікатори блоку і елемента - незалежні селектори. * Щоб сховати спінер під час завантаження, пиши явно: * .card--loading .card__spinner { display: none; } */

Де зустрічається

  • Yandex (де народився BEM) використовує його в кодовій базі понад 1 мільйон рядків: search__input--focused.
  • BBC News застосовує для модульних шаблонів: article__headline--breaking.
  • Shopify themes дотримуються BEM-іменування: product-form__input--error.
  • Google AMP використовує послідовно: amp-form__submit-button--disabled.

Питання на співбесіді

Q: Поясни block, element і modifier на прикладі e-commerce.
A: Block - cart. Element - cart__item (рядок у кошику). Modifier - cart__item--out-of-stock (перекреслена ціна, сірий текст). Кошик може бути де завгодно на сторінці. Item має сенс тільки всередині кошика.

Q: Як специфічність BEM порівняно зі звичайними класами?
A: .block__element має специфічність (0,1,0), як і .element. BEM сам по собі не підвищує вагу селектора. Захист дає унікальна назва, а не специфічність. !important не потрібен.

Q: Чи варто використовувати BEM у React-проєкті з CSS Modules?
A: Зазвичай ні. CSS Modules хешують назви класів при збірці, тому .button в одному файлі ніколи не зіткнеться з .button в іншому. BEM додасть багатослівності без вирішення проблеми, яку Modules вже вирішують.

Q: (Senior) Якщо блок має модифікатор і елемент всередині теж має модифікатор, один впливає на інший?
A: Ні. .card--loading змінює блок. .card__spinner--fast змінює елемент. Це незалежні селектори, браузер застосовує обидва. Якщо хочеш зупинити спінер під час завантаження, пиши явне правило: .card--loading .card__spinner { display: none; }.

Приклади

Базовий компонент кнопки

html
<button class="button button--primary"> <span class="button__icon"></span> Підтвердити </button> <button class="button button--secondary"> Скасувати </button>
css
.button { padding: 10px 20px; border: 1px solid transparent; cursor: pointer; } .button__icon { display: inline-block; margin-right: 6px; } .button--primary { background: #0057ff; color: #fff; } .button--secondary { background: #e0e0e0; color: #333; }

Дві кнопки, один блок, два модифікатори. Жоден стиль не виходить за межі .button.

Картка товару в інтернет-магазині

html
<article class="product-card product-card--featured"> <img class="product-card__image" src="shirt.jpg" alt="Футболка"> <h3 class="product-card__title">Бавовняна футболка</h3> <div class="product-card__price product-card__price--sale">$19.99</div> <button class="product-card__button product-card__button--add">Додати до кошика</button> </article>
css
.product-card { display: flex; flex-direction: column; max-width: 300px; } .product-card__image { width: 100%; height: 200px; object-fit: cover; } .product-card__title { font-size: 1.2em; margin: 10px 0; } .product-card__price { font-weight: bold; color: green; } .product-card__price--sale { color: red; text-decoration: line-through; } .product-card__button { padding: 10px; background: #007bff; color: #fff; border: none; } .product-card__button--add:hover { background: #0056b3; }

--featured на блоці дає змогу додати рамку або бейдж через один клас без зміни стилів елементів. --sale на __price робить ціну червоною з перекресленням. Жодне правило не потребує специфічності вищої за один клас.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?