Семантические классы > BEM

На протяжении многих лет я использовал BEM-подобный подход для написания CSS. Но я решил пойти другим путём — использовать семантические классы.

В итоге получилось гораздо меньше CSS, а писать его стало проще и предсказуемее.

Сегодня я хочу быстро рассказать, как это работает и почему я в восторге от такого подхода. Поехали!

/* Это Блок */
.button {
	/* ... */
}

Стили для дочерних элементов внутри блока — это элементы. Они используют паттерн именования .{block-class}__{child-element}.

Например, если у вас есть стили специально для иконок внутри .button, вы напишете:

/* Это Блок */
.button {
	/* ... */
}

/* Это Элемент */
.button__icon {
	/* ... */
}

Модификаторы — это классы, которые изменяют стиль блока или элемента. Они используют паттерн .{block-or-element-class}—{modifier}.

Например, пусть у кнопки есть цвет по умолчанию, а также несколько других вариантов. И иконки тоже могут быть разных размеров.

/* Это Блок */
.button {
	/* ... */
}

/* Это Элемент */
.button__icon {
	/* ... */
}

/* Это Модификаторы */
.button--primary {
	/* ... */
}

.button--secondary {
	/* ... */
}

.button__icon--large {
	/* ... */
}

.button__icon--small {
	/* ... */
}

Благословение и проклятие BEM

Цель системы вроде BEM — избежать конфликтов, которые возникают при использовании одинаковых имён классов в разных контекстах, и сделать очевидным назначение каждого класса.

И это действительно работает.

Но… это также приводит к огромному количеству дублирующегося CSS.

Возьмём, к примеру, .button—primary. Он, скорее всего:

  • задаёт background и border в цвет var(—primary),
  • меняет цвет текста на контрастный,
  • добавляет более тёмный оттенок при :hover и :active.

А если у вас есть .callout, .badge или .tabs, которым нужны те же цвета? Вам придётся дублировать эти стили для каждого компонента:

.callout {
	/* ... */
}

.callout--primary {
	/* ... */
}

.callout--secondary {
	/* ... */
}

Это нарушает принцип DRY (Don’t Repeat Yourself — «не повторяйся»). Именно так библиотеки вроде Bootstrap становятся такими большими.

А писать такой HTML — ещё и кошмар.

<div class="callout callout--secondary">
	<h2>Купите это прямо сейчас!</h2>
	<p>Сделайте это сейчас!</p>
	<button class="button button--primary">
		Ok! 
		<span class="icon icon--large">...</span>
	</button>
</div>

Все эти длиннющие имена классов — сплошная морока.

Есть способ проще.

Семантические классы

Альтернативный подход — использовать семантические классы.

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

.primary {
	background-color: var(--primary);
	border-color: var(--primary);
	color: var(--on-primary);
}

.secondary {
	background-color: var(--secondary);
	border-color: var(--secondary);
	color: var(--on-secondary);
}

.size-large {
	font-size: 1.2em;
}

.size-small {
	font-size: 0.8em;
}

Базовые стили компонентов всё ещё остаются:

.button {
	/* ... */
}

.icon {
	/* ... */
}

.callout {
	/* ... */
}

.badge {
	/* ... */
}

.tabs {
	/* ... */
}

Но теперь они комбинируются как Lego-блоки.

HTML становится чище и проще:

<div class="callout secondary">
	<h2>Купите это прямо сейчас!</h2>
	<p>Сделайте это сейчас!</p>
	<button class="button primary">
		Ok! 
		<span class="icon size-large">...</span>
	</button>
</div>

Когда я начал работать над Kelp, я сначала использовал BEM. Но очень быстро перешёл на семантические классы — и размер CSS-файла резко уменьшился.

Это не «лучше», а просто другой подход

Я поставил слово «лучше» в кавычки, потому что это не объективно лучший метод. Это другой способ, с другими целями и компромиссами.

Мне он подходит лучше, потому что он:

  • сокращает объём кода;
  • делает разметку проще;
  • ускоряет разработку.

У вас могут быть другие задачи — и это тоже нормально.

Вольный перевод статьи.

Автор статьи: Alex. Категория: CSS, HTML
Дата публикации: 10.10.2025