Именование классов в CSS: kebab-case, БЭМ и не только

Большинство имён классов в CSS пишут строчными буквами в kebab-case (.site-header, .price-tag), потому что так CSS называет и свои собственные сущности: background-color, font-size, flex-direction. БЭМ (BEM) добавляет к этому жёсткую структуру, block__element--modifier, так что по классу вроде card__title--large видно, к какому компоненту он относится, какая это часть компонента и какой вариант она показывает.
Ниже разберём, что в CSS чувствительно к регистру, а что нет, почему kebab-case стал стандартом, как на самом деле звучат правила именования БЭМ и когда другой подход делает БЭМ ненужным.
Что в CSS чувствительно к регистру, а что нет
Собственный синтаксис CSS не чувствителен к регистру в пределах ASCII, поэтому COLOR: RED работает точно так же, как color: red. Иначе устроены три вещи:
- Имена классов чувствительны к регистру.
.Cardне совпадёт сclass="card". Единственное исключение — страница в режиме совместимости (quirks mode), где браузеры сравнивают имена классов без учёта регистра; это ещё одна причина начинать каждую страницу с<!DOCTYPE html>. - Пользовательские свойства чувствительны к регистру.
--brand-colorи--Brand-Color— две разные переменные. - Имена HTML-элементов и атрибутов — нет. В HTML-документе
DIVиdivвыбирают одни и те же элементы. В XML они бы различались.
Проще всего раз и навсегда перестать об этом думать, если писать все имена классов и пользовательских свойств строчными буквами. Как с регистром обходятся другие языки и файловые системы, читайте в нашем сравнении чувствительности к регистру.
Почему в именах классов CSS победил kebab-case
Ничто в CSS не обязывает писать имена классов в kebab-case. .cardTitle и .card_title тоже допустимы. Дефисы логичны потому, что всё вокруг имени класса уже их использует: имена свойств, значения вроде space-between и HTML-атрибуты, такие как aria-label и data-user-id. Класс в camelCase посреди таблицы стилей с дефисами выглядит как опечатка.
Неудобства у kebab-case начинаются в JavaScript, потому что дефисы в идентификаторах запрещены. Свои случаи браузер обрабатывает сам: data-user-id читается как element.dataset.userId, а background-color превращается в element.style.backgroundColor. Имена классов обычно остаются строками (classList.add("is-open")), так что им это не мешает.
Исключение — CSS Modules. CSS-модуль экспортирует имена классов в JavaScript в виде объекта, поэтому .card-title приходится писать как styles["card-title"], а .cardTitle превращается в более аккуратное styles.cardTitle. Это единственная ситуация, в которой мы бы перешли на имена классов в camelCase.
Именование по БЭМ: блок, элемент, модификатор
БЭМ (Блок, Элемент, Модификатор; по-английски BEM) появился в Яндексе. Страница об истории методологии возводит эту работу к 2005 году, называет началом БЭМ выпуск фреймворка Яндекса Lego 2.0 в марте 2009 года, а переход к открытому исходному коду относит к 2010 году. Правила именования короткие:
- Блок: самостоятельный компонент, который имеет смысл сам по себе, например
.card,.search-formили.menu. - Элемент: часть блока, которая не имеет смысла вне его; имя строится так: блок, два подчёркивания, элемент —
.card__title,.menu__item. - Модификатор: флаг, который меняет внешний вид, поведение или состояние; имя строится так: блок или элемент, два дефиса, модификатор —
.card--featured,.card__button--disabled. Модификатор может быть и парой «ключ — значение»:.form--theme-xmas.
Слова внутри каждой части разделяются одиночными дефисами, как в .search-form__submit-button. Вот компонент целиком:
<article class="card card--featured">
<h2 class="card__title">Spring sale</h2>
<p class="card__body">Everything 20% off this week.</p>
<a class="card__button card__button--disabled" href="/sale">Shop now</a>
</article>.card { border: 1px solid #ddd; padding: 1rem; }
.card--featured { border-color: gold; }
.card__title { font-size: 1.25rem; }
.card__button--disabled { opacity: 0.5; pointer-events: none; }Классический БЭМ и стиль с двумя дефисами
Если заглянуть на официальный сайт методологии, bem.info, модификаторы там выглядят иначе. В классической схеме модификатор отделяется одним подчёркиванием: menu_hidden, menu__item_visible, menu__item_type_radio. Вариант с -- указан там как альтернативный, «Two Dashes style» (стиль «два дефиса»), и именно его описывает getbem.com. Оба варианта — полноценный БЭМ; главное, не смешивайте их в одной кодовой базе. bem.info упоминает и небольшой подвох с дефисами: двойной дефис внутри HTML-комментария может не пройти валидацию.
Правила БЭМ, в которых легко ошибиться
- Никаких элементов элементов. Заголовок внутри шапки карточки — это всё равно
card__title, а неcard__header__title. В HTML элементы можно вкладывать сколь угодно глубоко, но имена остаются плоскими. Довод bem.info: с цепочками в именах нельзя переместить, добавить или удалить элемент, не меняя существующий код. - Модификатор никогда не стоит один. Пишите
class="button button--primary", а неclass="button--primary". Модификатор лишь дополняет базовый класс. - Используйте селекторы из одного класса.
.card__title, а не.card .card__titleилиh2.card__title. Общепринятое исключение — модификатор блока, который меняет его элементы:.card--featured .card__title. - Называйте модификаторы по назначению. Это наше мнение, а не правило БЭМ:
--featuredили--errorпереживут редизайн, а--yellowстанет неправдой в тот день, когда сменится фирменный цвет. - Не цепляйте JavaScript к классам для стилей. Это тоже наше мнение: в скриптах выбирайте элементы по data-атрибуту вроде
data-action="close", а не по.card__button, тогда переименование класса ради оформления не сломает поведение.
Переименовать блок позже — по большей части задача для поиска и замены. Вставьте шаблон в наш инструмент «Найти и заменить», включите режим «Регулярка» и замените card(__|--) на product-card$1: все классы элементов и модификаторов перейдут на новое имя блока, а счётчик совпадений покажет, сколько их изменилось. Сам класс card переименуйте отдельно, чтобы не зацепить посторонние слова.
БЭМ в Sass и во встроенной вложенности CSS
Родительский селектор Sass умеет добавлять суффикс к внешнему селектору, и в документации Sass в качестве примера приводится именно БЭМ. Встроенная вложенность CSS так не умеет, и MDN прямо об этом предупреждает: вложенный &__title читается как селектор типа, поэтому .card__title из него не получится.
// Sass: works
.card {
&__title { font-size: 1.25rem; } // compiles to .card__title
&--featured { border-color: gold; } // compiles to .card--featured
}/* Native CSS nesting: write the full class names */
.card {
border: 1px solid #ddd;
&.card--featured { border-color: gold; } /* .card.card--featured */
}
.card__title { font-size: 1.25rem; }За пределами БЭМ: когда он не нужен
БЭМ нужен, чтобы имена классов не конфликтовали и чтобы структура была видна в глобальной таблице стилей. Если проблему конфликтов решает что-то другое, длинные имена дают меньше выгоды:
- CSS Modules по умолчанию делают имена классов локальными, поэтому простой
.titleв одном компоненте не столкнётся с.titleв другом. - Utility-first CSS, например Tailwind, в основном заменяет собственные имена классов готовыми утилитами вроде
font-bold, так что именование переезжает в ваши компоненты. - Небольшим сайтам с несколькими компонентами часто хватает простого kebab-case и префикса для каждого компонента.
Но и тогда kebab-case остаётся надёжным выбором по умолчанию для любого имени класса, которое вы всё-таки пишете.
Практические правила
- Строчный kebab-case для имён классов и пользовательских свойств, всегда.
- БЭМ, когда одной глобальной таблицей стилей пользуется много людей:
block__element--modifier, плоские имена элементов, модификаторы рядом с базовым классом. - Имена классов в camelCase, только если инструмент вроде CSS Modules передаёт их в JavaScript.
Нужно превратить название компонента в класс? Введите «Product Card» в наш конвертер регистра, и кнопка kebab-case выдаст product-card. Соглашения для всех остальных слоёв проекта, от переменных окружения до URL, собраны в нашей шпаргалке по стилям именования для разработчиков.


