CSS の命名規則:kebab-case と BEM の基本、BEM が要らない場面まで

CSS のクラス名の多くは、小文字の kebab-case(ケバブケース)で書かれます(.site-header、.price-tag)。CSS 自身がそうやって名前を付けているからです。background-color、font-size、flex-direction がその例です。BEM はこれを土台に、block__element--modifier という決まった構造を加えます。そのため card__title--large のようなクラスを見れば、どのコンポーネントに属し、そのどの部分で、どのバリエーションを表しているかがわかります。
この記事では、CSS で大文字・小文字が区別されるもの・されないもの、kebab-case が標準になった理由、BEM の命名ルールが実際に定めている内容、そして別の手法を使えば BEM が要らなくなる場面を順に見ていきます。
CSS で大文字・小文字が区別されるもの、されないもの
CSS 自体の構文は ASCII の範囲では大文字・小文字を区別しないので、COLOR: RED は color: red とまったく同じように動きます。ただし、次の 3 つは事情が違います。
- クラス名は区別されます。
.Cardはclass="card"に一致しません。唯一の例外は quirks モードのページで、ブラウザはクラス名を大文字・小文字を区別せずに照合します。どのページも<!DOCTYPE html>で始めるべき理由が、また 1 つ増えるわけです。 - カスタムプロパティは区別されます。
--brand-colorと--Brand-Colorは別々の変数です。 - HTML の要素名と属性名は区別されません。HTML 文書では、
DIVとdivは同じ要素を選択します。XML なら別物になります。
この問題を二度と考えずに済む最も簡単な方法は、クラス名とカスタムプロパティをすべて小文字にしておくことです。ほかの言語やファイルシステムでの大文字・小文字の扱いについては、大文字・小文字の区別の比較記事をご覧ください。
CSS のクラス名で kebab-case が主流になった理由
CSS には、クラス名に kebab-case を強制する仕組みはありません。.cardTitle も .card_title も有効です。ハイフンが理にかなっているのは、クラス名の周りにあるものがすでにハイフンを使っているからです。プロパティ名、space-between のような値、aria-label や data-user-id といった HTML 属性がそうです。ハイフンでつながれた名前が並ぶスタイルシートに camelCase(キャメルケース)のクラスがあると、タイプミスに見えてしまいます。
kebab-case が扱いにくくなるのは JavaScript です。識別子にハイフンを使えないからです。ブラウザ自身のものは、ブラウザが変換してくれます。data-user-id は element.dataset.userId として読み取られ、background-color は element.style.backgroundColor になります。クラス名はたいてい文字列のまま扱う(classList.add("is-open"))ので、問題になりません。
例外は CSS Modules です。CSS Module はクラス名をオブジェクトとして JavaScript にエクスポートするので、.card-title は styles["card-title"] と書く必要がありますが、.cardTitle ならすっきりした styles.cardTitle になります。私たちがクラス名を camelCase に切り替えるのは、この構成のときだけです。
BEM の命名:ブロック、エレメント、モディファイア
BEM(Block, Element, Modifier)は Yandex で生まれました。この手法の歴史を紹介するページによると、取り組みは 2005 年までさかのぼり、2009 年 3 月の Yandex の Lego 2.0 フレームワークのリリースを BEM の始まりとし、オープンソース化は 2010 年とされています。命名ルールは短いものです。
- ブロック:それだけで意味を持つ独立したコンポーネントです。
.card、.search-form、.menuなどがあります。 - エレメント:ブロックの一部で、ブロックの外では意味を持たないものです。ブロック名、アンダースコア 2 つ、エレメント名の順で名前を付けます(
.card__title、.menu__item)。 - モディファイア:見た目、振る舞い、状態を変えるフラグです。ブロック名またはエレメント名、ハイフン 2 つ、モディファイア名の順で名前を付けます(
.card--featured、.card__button--disabled)。モディファイアはキーと値の形にもできます(.form--theme-xmas)。
各部分の中の単語は、.search-form__submit-button のように 1 つのハイフンで区切ります。コンポーネント全体の例を示します。
<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 と「Two Dashes」スタイル
この手法の公式サイト bem.info を読むと、モディファイアの書き方が違うことに気づきます。クラシックな方式では、モディファイアを 1 つのアンダースコアで区切ります(menu_hidden、menu__item_visible、menu__item_type_radio)。-- を使う書き方は、そこでは代替案の「Two Dashes style」として紹介されており、getbem.com が解説しているのはこちらです。どちらも正当な BEM ですが、1 つのコードベースで混ぜないようにしましょう。bem.info は、ダッシュを使う書き方の小さな落とし穴にも触れています。HTML コメントの中に二重ハイフンがあると、バリデーションに失敗することがあるのです。
間違えやすい BEM のルール
- エレメントのエレメントは作らない。カードのヘッダー内にあるタイトルも
card__titleであり、card__header__titleではありません。HTML ではエレメントをいくらでも深く入れ子にできますが、名前はフラットなままにします。bem.info によれば、名前を連ねてしまうと、既存のコードを変更せずにエレメントを移動、追加、削除できなくなるからです。 - モディファイアは単独で使わない。
class="button--primary"ではなく、class="button button--primary"と書きます。モディファイアは基本のクラスに付け加えるだけのものです。 - 単一クラスのセレクターを使う。
.card .card__titleやh2.card__titleではなく、.card__titleと書きます。例外として認められているのは、ブロックのモディファイアがそのエレメントを変える場合です(.card--featured .card__title)。 - モディファイアには目的で名前を付ける。これは BEM のルールではなく私たちの意見です。
--featuredや--errorはリデザインを経ても通用しますが、--yellowはブランドカラーが変わった日に嘘になります。 - JavaScript のフックをスタイル用のクラスに混ぜない。これも私たちの意見です。スクリプトで要素を選択するときは、
.card__buttonではなくdata-action="close"のような data 属性を使いましょう。そうすれば、スタイルの都合でクラス名を変えても動作が壊れません。
後からブロック名を変える作業は、ほとんどが検索と置換で済みます。テンプレートを当サイトの検索と置換ツールに貼り付け、正規表現モードをオンにして、card(__|--) を product-card$1 に置換します。すべてのエレメントとモディファイアのクラスが新しいブロック名に移り、一致件数を見れば何か所変わったかがわかります。無関係な単語まで巻き込まないよう、単独の card クラスは別途名前を変えてください。
Sass とネイティブ CSS のネストで BEM を書く
Sass の親セレクターは外側のセレクターにサフィックスを付け足せるので、Sass のドキュメントでも BEM が例に使われています。ネイティブの 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; }BEM の先へ:BEM が要らない場面
BEM は、グローバルなスタイルシートでクラス名の衝突を防ぎ、構造をひと目でわかるようにするためのものです。ほかの何かが衝突の問題を解決してくれるなら、BEM の長い名前から得られるものは少なくなります。
- CSS Modules はデフォルトでクラス名をローカルスコープにするので、あるコンポーネントのただの
.titleが、別のコンポーネントの.titleと衝突することはありません。 - ユーティリティファーストの CSS(Tailwind など)は、独自のクラス名の大部分を
font-boldのような定義済みのユーティリティに置き換えるので、命名の問題はコンポーネント側に移ります。 - 小規模なサイトでコンポーネントが数個しかないなら、普通の kebab-case とコンポーネントごとのプレフィックスで十分なことがよくあります。
その場合でも、自分で書くクラス名については、kebab-case が安全な標準であることに変わりはありません。
目安となるルール
- クラス名とカスタムプロパティは、常に小文字の kebab-case にする。
- 大勢で 1 つのグローバルなスタイルシートを共有するなら BEM を使う。
block__element--modifierの形で、エレメント名はフラットにし、モディファイアは基本のクラスと並べて付ける。 - camelCase のクラス名は、CSS Modules のようなツールがクラス名を JavaScript に公開する場合に限る。
コンポーネント名をクラス名にしたいときは、当サイトの文字ケース変換ツールに「Product Card」と入力して kebab-case ボタンを押せば、product-card が得られます。環境変数から URL まで、プロジェクトのほかの層の命名規則については、開発者向けの命名規則チートシートをご覧ください。


