← HTML & CSS/BEM: методология именования классов#38 из 383← ПредыдущийСледующий →+15 XP
Полезно по теме:Гайд: старт в frontendПрактика: DOM и событияТермин: DOMМаршрут: старт с нуля

BEM: методология именования классов

Когда проект растёт, CSS превращается в хаос: класс .button переопределяется в пяти местах, .title значит разное в разных компонентах, специфичность растёт бесконтрольно. BEM — методология именования, которая решает эти проблемы структурно.

Block, Element, Modifier

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>

Синтаксис CSS

/* Блок */
.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 { }   /* Один класс не может быть и тем и другим */

Альтернативы BEM

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 vs Utility-first

| 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: методология именования классов

Когда проект растёт, CSS превращается в хаос: класс .button переопределяется в пяти местах, .title значит разное в разных компонентах, специфичность растёт бесконтрольно. BEM — методология именования, которая решает эти проблемы структурно.

Block, Element, Modifier

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>

Синтаксис CSS

/* Блок */
.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 { }   /* Один класс не может быть и тем и другим */

Альтернативы BEM

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 vs Utility-first

| 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)`.

Загружаем среду выполнения...
Загружаем AI-помощника...