Когда проект растёт, CSS превращается в хаос: класс .button переопределяется в пяти местах, .title значит разное в разных компонентах, специфичность растёт бесконтрольно. BEM — методология именования, которая решает эти проблемы структурно.
Block — самостоятельный компонент, имеет смысл сам по себе:
<div class="card">...</div>
<nav class="navigation">...</nav>
<button class="button">...</button>Element — часть блока, без него не имеет смысла. Разделяется __:
<div class="card">
<img class="card__image">
<div class="card__body">
<h2 class="card__title">...</h2>
<p class="card__description">...</p>
</div>
<button class="card__button">...</button>
</div>Modifier — вариант или состояние блока/элемента. Разделяется --:
<!-- Модификатор блока -->
<div class="card card--featured">...</div>
<div class="card card--compact">...</div>
<!-- Модификатор элемента -->
<button class="card__button card__button--disabled">...</button>
<button class="button button--primary button--large">Кнопка</button>/* Блок */
.card { }
/* Элемент */
.card__title { }
.card__image { }
/* Модификатор блока */
.card--featured { }
.card--compact { }
/* Модификатор элемента */
.card__button--disabled { }Важно: не вкладывай CSS-селекторы. .card .card__title { } — это нарушение BEM. Используй .card__title { } напрямую.
1. Низкая специфичность — все классы одинаково специфичны (один класс)
2. Нет конфликтов — .card__title не пересекается с .article__title
3. Самодокументация — из имени класса понятно, где он живёт
4. Независимость — блок можно перенести в любое место, он сохранит стили
/* Ошибка: элемент элемента — не существует в BEM */
.card__body__title { } /* Неверно */
.card__title { } /* Верно — элемент всегда принадлежит БЛОКУ */
/* Ошибка: вложение CSS */
.card .card__title { } /* Нарушает независимость */
.card__title { } /* Верно */
/* Ошибка: блок и элемент одновременно */
.card card__content { } /* Один класс не может быть и тем и другим */SMACSS (Scalable and Modular Architecture): делит стили на Base, Layout, Module, State, Theme — категоризация по роли.
OOCSS (Object Oriented CSS): разделяет структуру и визуальное оформление. .media { } + .media--large { }.
Utility-first (Tailwind CSS): атомарные классы. Вместо .card__title--large пишешь text-xl font-bold. Меньше CSS, больше HTML.
| BEM | Utility-first |
|-------------------------------|------------------------------------|
| Описательные имена компонентов | Атомарные утилиты |
| Легко читать HTML | Легко менять стили без CSS |
| Переиспользуемые компоненты | Нет абстракций |
| Хорошо для дизайн-систем | Хорошо для прототипирования |
| Большие CSS-файлы | Маленький финальный CSS (PurgeCSS) |
На практике: BEM для компонентных библиотек и дизайн-систем; Tailwind для приложений с быстрым итерационным циклом.
Парсер BEM-классов: определяет блок, элемент и модификатор из строки класса
// BEM-парсер: разбирает имена классов на составляющие
function parseBEM(className) {
// BEM паттерн: block__element--modifier
// Регуляризируем: блок — только [a-z][a-z0-9-]*
const elementSep = '__'
const modSep = '--'
let block = className
let element = null
let modifier = null
// Ищем модификатор (последний --)
const modIdx = className.lastIndexOf(modSep)
if (modIdx !== -1 && modIdx > 0) {
modifier = className.slice(modIdx + modSep.length)
block = className.slice(0, modIdx)
}
// Ищем элемент (__)
const elemIdx = block.indexOf(elementSep)
if (elemIdx !== -1) {
element = block.slice(elemIdx + elementSep.length)
block = block.slice(0, elemIdx)
}
return { block, element, modifier }
}
function buildBEM(block, element = null, modifier = null) {
let name = block
if (element) name += `__${element}`
if (modifier) name += `--${modifier}`
return name
}
function validateBEM(className) {
const valid = /^[a-z][a-z0-9-]*(__[a-z][a-z0-9-]*)?(--[a-z][a-z0-9-]*)?$/.test(className)
return valid
}
// Тесты
const testCases = [
'card',
'card__title',
'card__button--disabled',
'navigation__item--active',
'button--primary',
'Card', // Нарушение: заглавная буква
'card__title__sub', // Нарушение: элемент элемента
'card--', // Нарушение: пустой модификатор
]
console.log('=== Парсинг BEM-классов ===')
testCases.forEach(cls => {
const parsed = parseBEM(cls)
const valid = validateBEM(cls)
console.log(`"${cls}"`)
console.log(` block: ${parsed.block}, element: ${parsed.element}, modifier: ${parsed.modifier}`)
console.log(` valid: ${valid}`)
})
console.log('\n=== Построение BEM-классов ===')
console.log(buildBEM('card')) // 'card'
console.log(buildBEM('card', 'title')) // 'card__title'
console.log(buildBEM('card', 'button', 'disabled')) // 'card__button--disabled'
console.log(buildBEM('button', null, 'primary')) // 'button--primary'Когда проект растёт, CSS превращается в хаос: класс .button переопределяется в пяти местах, .title значит разное в разных компонентах, специфичность растёт бесконтрольно. BEM — методология именования, которая решает эти проблемы структурно.
Block — самостоятельный компонент, имеет смысл сам по себе:
<div class="card">...</div>
<nav class="navigation">...</nav>
<button class="button">...</button>Element — часть блока, без него не имеет смысла. Разделяется __:
<div class="card">
<img class="card__image">
<div class="card__body">
<h2 class="card__title">...</h2>
<p class="card__description">...</p>
</div>
<button class="card__button">...</button>
</div>Modifier — вариант или состояние блока/элемента. Разделяется --:
<!-- Модификатор блока -->
<div class="card card--featured">...</div>
<div class="card card--compact">...</div>
<!-- Модификатор элемента -->
<button class="card__button card__button--disabled">...</button>
<button class="button button--primary button--large">Кнопка</button>/* Блок */
.card { }
/* Элемент */
.card__title { }
.card__image { }
/* Модификатор блока */
.card--featured { }
.card--compact { }
/* Модификатор элемента */
.card__button--disabled { }Важно: не вкладывай CSS-селекторы. .card .card__title { } — это нарушение BEM. Используй .card__title { } напрямую.
1. Низкая специфичность — все классы одинаково специфичны (один класс)
2. Нет конфликтов — .card__title не пересекается с .article__title
3. Самодокументация — из имени класса понятно, где он живёт
4. Независимость — блок можно перенести в любое место, он сохранит стили
/* Ошибка: элемент элемента — не существует в BEM */
.card__body__title { } /* Неверно */
.card__title { } /* Верно — элемент всегда принадлежит БЛОКУ */
/* Ошибка: вложение CSS */
.card .card__title { } /* Нарушает независимость */
.card__title { } /* Верно */
/* Ошибка: блок и элемент одновременно */
.card card__content { } /* Один класс не может быть и тем и другим */SMACSS (Scalable and Modular Architecture): делит стили на Base, Layout, Module, State, Theme — категоризация по роли.
OOCSS (Object Oriented CSS): разделяет структуру и визуальное оформление. .media { } + .media--large { }.
Utility-first (Tailwind CSS): атомарные классы. Вместо .card__title--large пишешь text-xl font-bold. Меньше CSS, больше HTML.
| BEM | Utility-first |
|-------------------------------|------------------------------------|
| Описательные имена компонентов | Атомарные утилиты |
| Легко читать HTML | Легко менять стили без CSS |
| Переиспользуемые компоненты | Нет абстракций |
| Хорошо для дизайн-систем | Хорошо для прототипирования |
| Большие CSS-файлы | Маленький финальный CSS (PurgeCSS) |
На практике: BEM для компонентных библиотек и дизайн-систем; Tailwind для приложений с быстрым итерационным циклом.
Парсер BEM-классов: определяет блок, элемент и модификатор из строки класса
// BEM-парсер: разбирает имена классов на составляющие
function parseBEM(className) {
// BEM паттерн: block__element--modifier
// Регуляризируем: блок — только [a-z][a-z0-9-]*
const elementSep = '__'
const modSep = '--'
let block = className
let element = null
let modifier = null
// Ищем модификатор (последний --)
const modIdx = className.lastIndexOf(modSep)
if (modIdx !== -1 && modIdx > 0) {
modifier = className.slice(modIdx + modSep.length)
block = className.slice(0, modIdx)
}
// Ищем элемент (__)
const elemIdx = block.indexOf(elementSep)
if (elemIdx !== -1) {
element = block.slice(elemIdx + elementSep.length)
block = block.slice(0, elemIdx)
}
return { block, element, modifier }
}
function buildBEM(block, element = null, modifier = null) {
let name = block
if (element) name += `__${element}`
if (modifier) name += `--${modifier}`
return name
}
function validateBEM(className) {
const valid = /^[a-z][a-z0-9-]*(__[a-z][a-z0-9-]*)?(--[a-z][a-z0-9-]*)?$/.test(className)
return valid
}
// Тесты
const testCases = [
'card',
'card__title',
'card__button--disabled',
'navigation__item--active',
'button--primary',
'Card', // Нарушение: заглавная буква
'card__title__sub', // Нарушение: элемент элемента
'card--', // Нарушение: пустой модификатор
]
console.log('=== Парсинг BEM-классов ===')
testCases.forEach(cls => {
const parsed = parseBEM(cls)
const valid = validateBEM(cls)
console.log(`"${cls}"`)
console.log(` block: ${parsed.block}, element: ${parsed.element}, modifier: ${parsed.modifier}`)
console.log(` valid: ${valid}`)
})
console.log('\n=== Построение BEM-классов ===')
console.log(buildBEM('card')) // 'card'
console.log(buildBEM('card', 'title')) // 'card__title'
console.log(buildBEM('card', 'button', 'disabled')) // 'card__button--disabled'
console.log(buildBEM('button', null, 'primary')) // 'button--primary'Создай компонент карточки товара по методологии BEM. Блок — `.card`, элементы — `.card__image`, `.card__body`, `.card__title`, `.card__price`, `.card__button`. Модификаторы — `.card--featured` (выделенная карточка с другим фоном) и `.card__button--primary`. Напиши HTML разметку и CSS стили.
BEM: блок — самостоятельный компонент `.card`, элемент — часть блока `.card__title` (двойное подчёркивание), модификатор — вариант `.card--featured` (двойное тире). `card__button--primary`: фон `#7b2ff7`, цвет `white`. `card--featured`: `border-color: #7b2ff7`, `box-shadow: 0 4px 12px rgba(123,47,247,0.2)`.