Що таке методологія 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 не потрібно.
Швидкий приклад
<button class="button button--primary">
<span class="button__icon">★</span>
Натисни
</button>.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 не виправданий.
Порівняння
| Аспект | BEM | Flat CSS (.btn.active) | CSS Modules |
|---|---|---|---|
| Іменування | block__element--mod | .class, .state | Button_module__root |
| Масштабованість | Висока (простір імен) | Низька (глобальні колізії) | Висока (хешування при збірці) |
| Розмір команди | 5+ розробників | Соло або мала команда | Будь-який |
| Дебаг | DevTools показує ієрархію | Шукай глобальні стилі | Безпечно, але назви нечитабельні |
| Найкраще для | Vanilla або командний CSS | Прототипи | React/Vue компоненти |
Як браузер обробляє BEM
Жодної магії. Браузер читає BEM-класи як звичайні CSS-селектори. .button__element має специфічність (0,1,0), тобто рівно стільки ж, скільки й будь-який одиночний клас. Ось і все. Структура закодована в назві, а не в каскаді чи shadow DOM. CSS пишуть плоско, ніяких .button .button__icon {}.
Типові помилки
Вкладені селектори в CSS:
/* Неправильно - ламається без PostCSS, суперечить BEM */
.button {
.icon { color: red; }
}
/* Правильно - завжди плоскі селектори */
.button__icon { color: red; }Занадто довгий ланцюжок модифікаторів:
/* Неправильно - важко читати і перевизначити */
.button--primary--large--rounded { }
/* Правильно - окремі класи, комбінуй в HTML */
.button--primary.button--large { }Вкладені елементи:
/* Неправильно - BEM іде тільки на один рівень для елементів */
.header__nav__link { }
/* Правильно - link це елемент nav */
.nav__link { }Очікування, що модифікатор блоку вплине на модифікатор елемента:
.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; }.
Приклади
Базовий компонент кнопки
<button class="button button--primary">
<span class="button__icon">★</span>
Підтвердити
</button>
<button class="button button--secondary">
Скасувати
</button>.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.
Картка товару в інтернет-магазині
<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>.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 робить ціну червоною з перекресленням. Жодне правило не потребує специфічності вищої за один клас.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.