BEMによるフロントエンドの設計 第1回 基本概念とルール
この記事ではフロントエンドの設計方法「BEM」を紹介します。第1回目はBEMのもっとも基本となるBlock、Element、Modifierの概念と、class名の命名ルールを解説しています。
はじめに
最近フロントエンド界隈で、『BEM』という言葉を見かけることが増えてきました。BEMとは、Block、Element、Modifierの略語です。Webサイトのコンポーネント化のためのフロントエンド設計方法のひとつで、厳格なclass名の命名ルールが特徴的な手法です。
第1回は、BEMをまったく知らない方向けの入門編です。
なぜBEMが必要なのか
私たちはHTMLとCSSを使うことでしか、Webサイトを作ることができませんが、HTMLとCSSにはプログラム的な機能が備わっていません。そのために、フロントエンドエンジニアは次のようなトラブルに遭遇することがままあります。
スタイルの優先順位のコントロール不能状態
id属性を使ったスタイル指定や、深すぎる入れ子は、スタイルの優先順位のコントロールが難しくなります。そこで、禁忌とされる!important
の多用を誘発することとなり、その結果、さらに優先度のコントロールが難しくなってしまいます。
汎用的に使えないスタイル
HTMLの要素セレクタに直接スタイルが指定されていると、例えば、同じ見た目のまま見出しレベルだけを変えてスタイルを流用したい場合や、メニューの現在地だけリンクを外しつつスタイルは維持したいという場合に、CSSに手を加える必要があります。
.box h2 { border: 3px solid gray; }
例えば、上記のような指定をしていると、同じスタイルのまま見出しレベルをh3にしたい場合、.box h2, .box h3 { ... }
のようにセレクタを追加しなければなりません。さらに、同じ.box
内に別のデザインの見出しを追加したい場合、border: none;
などで上記のスタイルを打ち消さねばなりません。
もし要素セレクタでなく、class属性を使ってスタイルを指定していれば、このような問題は起こりません。
パーツを別の場所に流用すると表示が崩れる
パーツ単体で独立しておらず、別のパーツに依存したスタイル指定になっていると、パーツを別の場所に移動した場合に、表示が崩れることがあります。
.entry-page .title { border: 3px solid gray; }
例えば、上記のCSSの場合、.title
だけを別の場所に流用したいけれど、流用先は.entry-page
ではない場合、このスタイルを流用することができません。
もし.title
だけを独立したパーツとしていれば、どこにでも流用できます。
制作者本人しかわからないclass名の命名ルール
class名の命名ルールが決まっていないと、他の人がそのHTMLを見たときに、どこからどこまでがひとつのパーツのまとまりなのかわかりません。
パーツの流用時、JavaScriptが動かない
JavaScriptの挙動をセットにしてパーツ化されていないと、そのパーツを別の画面に流用したときに、該当するCSSとJavaScriptを正確にコピーできないことがあります。そうすると、スタイルはきちんと当たっているけれど、JavaScriptが動作しないといったトラブルが起こる可能性があります。
BEMで設計することで、これらの問題を解決できます。
BEMが解決を目指すポイント
BEMの公式ドキュメントでは、BEMという方法論が、どのような目標を達成すべく開発されたのかについて、次の3点を挙げています。
長期間メンテナンスできる設計で、ファーストバージョンの開発をすばやく
開発スピードが速く、しかも長期間メンテナンスしやすい設計を実現するには、設計ルールを明確に規定する必要があります。ルールを明確に規定することで、class名の命名ルールやファイル構造などで、個人個人が悩む時間が少なくなり、その分開発スピードが上がることが期待できます。
チームのスケーラビリティ
チームの規模拡大が容易であるためには、チームに入った新しいメンバーが、できるだけ短期間で即戦力になれる必要があります。チームに新しいメンバーが入っても、そのメンバーがBEMさえ理解していれば即戦力になります。また、時間がたってもチームが変わってもメンテナンス可能な状態を維持できるでしょう。BEMはチームの共通言語の役割をします。
コードの再利用性
コードの再利用性は、Webサイトを構成するパーツがどの程度、独立して設計されているかで決まります。パーツを完全に独立させることができれば、コードの再利用が容易になります。ここでの再利用とは、CSSによる見た目の話だけでなく、JavaScriptによる振る舞いも含んでいます。
BEMで設計する目的
BEMはもともと、プロジェクトの成長にともなって、既存のページが変化していくことを前提として考え出されました。また、変更がある場合は、その対応コストをできるだけ低減することを目的としています。
BEMは多くの人がかかわり、長く運用されるプロジェクトに、特に向いていると言えるでしょう。キャンペーンページなどの期間限定でしか使われないサイトでも、開発をすばやく行えるため、BEMを利用する価値はあるでしょう。
BEMを構成する3つの要素
BEMは、Block、Element、Modifierという3つの概念だけ理解してしまえば、あとはclass名の命名ルールに則って記述するだけの単純な方法です。
まずは、Block、Element、Modifierが、それぞれどのような関係にあるのかを、簡単に説明します。
世の中に存在する多くのWebページは、ヘッダー、ナビゲーション、商品説明、フッターなどといった、パーツの集まりで構成されています。BEMでは、これらのパーツをBlockと呼びます。
1つのBlockは、Block自身を構成する部品のようなものを有しています。例えば、検索フォームBlockなら、「入力フィールド」と「ボタン」の2つの部品で構成されています。これをElementと呼びます。
同じBlockであっても、通常の状態とエラー状態などのように、装飾が異なることもあるでしょう。BEMでは、そうした装飾に関する調整を、Modifierと呼びます。
だいたいの関係がわかったら、以降、実際のコード例に沿って、それぞれをどのような考え方で設計するのかを解説していきます。
Block(ブロック)
例えば、次のようなコードの場合、searchという名前のBlockに、inputと、buttonという2つのElementが属していることになります。
<div class="search"> <!--Block-->
<input class="search__input" type="text"> <!--Element-->
<input class="search__button" type="submit"> <!--Element-->
</div>
一般的なWebページの場合、Blockは何度も繰り返し出現することが想定できます。そのためBlockを識別するためにはid属性を使わずclass属性を利用します。
Blockはどこにでも置くことができ、Blockの中にBlockを含めることも可能です。ただし、CSSではBlockを入れ子にしてスタイルを指定してはいけません。
なぜなら、Blockは完全に独立し、Blockを別の場所に移動しても、単体で動作可能である必要があるからです。
特定の条件によりスタイルを変更するには、後述するModifierを利用します。
Element(エレメント)
ElementはBlockの構成要素で、そのElementが属するBlock内でのみ意味を成します。
<div class="search"> <!--Block-->
<input class="search__input" type="text"> <!--Element-->
<input class="search__button" type="submit"> <!--Element-->
</div>
Elementのclass名には、必ず所属するBlock名を含めます。
.search { ... }
.search__input { ... }
.search__button { ... }
Block名を含めることでclass名は冗長になりますが、次のようなメリットがあります。
- CSS上でセレクタを入れ子にする必要がなくなり、スタイルの優先度で頭を悩ませる必要がなくなる。
- ルールが統一されるため、HTMLを見ただけで、どこからどこまでがひとつのBlockなのかがわかりやすくなる。
ひとつのElementはBlock内で何度も繰り返し利用できます。
Modifier(モディファイア)
既存のBlockやElementと似ているけれど、見た目や動きが少しだけ違うものを作りたい場合には、新規にそれらを作るのではなく、Modifierを使うことができます。
例えば、同じリストで、行頭記号が2種類以上存在する場合や、同じメニュー内の「タブ」であっても、現在ユーザーがいるタブだけ、見栄えを変える場合などです。
Modifierは、BlockやElementのバリエーションの軸(例:行頭記号)や、状態(例:現在地)を表すプロパティの役割をします。
Modifierは名前(key)と値(value)を持ち、複数のModifierを同時に使用することができます。
Modifierには、Blockに対するModifierと、Elementに対するModifierがあります。
BlockのModifier
さきほどの例で考えると、同じリストで、行頭記号が2種類以上存在する場合です。リストというBlockにModifierが必要です。
この場合のclass名は「Block_key_value」で表します。
次のようなコードの場合、listというclass名のBlockに、typeのバリエーションがあり、それはdiscとcheckという2種類だ、ということがわかります。
<ul class="list list_type_disc">
<li class="list__item"></li>
<li class="list__item"></li>
...
</ul>
<ul class="list list_type_check">
<li class="list__item"></li>
<li class="list__item"></li>
...
</ul>
ElementのModifier
同じメニュー内の「タブ」であっても、現在ユーザーがいるタブだけ、見栄えを変える場合は、タブというElementに対するModifierが必要です。
この場合のclass名は「Element_key_value」で表します。
次のようなコードの場合、menu__itemというclass名のElementに、state(状態)が違うものがあり、それはcurrentという現在地の状態を表すものだということがわかります。
<ul class="menu">
<li class="menu__item"></li>
<li class="menu__item menu__item_state_current"></li>
<li class="menu__item"></li>
</ul>
class名の命名ルールとセパレーター
これまでのサンプルコードの中で、クラス名の中に__
というように、アンダースコアを二重に使っていることに気付くでしょう。この__
や、_
、-
は、単語と単語を区切るために使用していて、BEMではこれをセパレーターと呼んでいます。
BEMでは、誰が見てもわかるように、Block、Element、Modifierそれぞれの区切りに、一貫したセパレーターを使うことが重要とされます*。
*注:セパレーター
BEMは専用のツールを使ってWebサイトを生成する場合もあり、その場合はツールによって決められた、所定のセパレーターを使うことが重要となります。ですが、ここではツールを使わないことを前提として解説しています。どちらにしても、セパレーターを統一することは重要です。
セパレーターの種類
ここでは、BEM開発者の記述方法に合わせたセパレーターを紹介していますが、次の3種類の区切りをきちんと区別でき、それが一貫していれば、セパレーターの種類は自由です。
- BlockとElementとの区切り
- Block(あるいはElement)とModifierとの区切りと、Modifierのkeyとvalueの区切り
- BlockやElement名を、2つ以上の単語で表す場合の単語と単語との区切り
大切なのは、クラス名を見たときに、セパレーターの種類によって、それがBlockとElementの区切りなのか、Modifierの区切りなのか、それともただの単語の区切りなのかが、はっきり判別できるようにすることです。
BlockとElementの区切り
「Block__
Element」と、区切り文字にアンダースコアを2つ繋げたものを使用します。
<ul class="menu">
<li class="menu__item"></li>
<li class="menu__item"></li>
</ul>
Modifierの区切りと、Modifierのkeyとvalueの区切り
「Block_
key_
value」または「Element_
key_
value」のように、区切り文字にアンダースコア1つを使用します。
<ul class="list list_type_disc">
<li class="list__item"></li>
<li class="list__item"></li>
...
</ul>
<ul class="menu">
<li class="menu__item"></li>
<li class="menu__item menu__item_state_current"></li>
<li class="menu__item"></li>
</ul>
単語の区切り
BlockやElement名を、2つ以上の単語で表す場合は「単語-
単語」のように、区切り文字にハイフンを使用します。
<div class="search-result">
<div class="search-result__item">...</div>
</div>
コラム:キャメルケースや別種のセパレーターの利用
単語の区切りにハイフン( - )を利用せず、キャメルケース(searchResultなど)にすると、BEMのセパレーターとの区別がつきやすくなります。
<div class="searchResult">
<div class="searchResult__item">...</div>
</div>
また、アンダースコアや連続セパレーターにこだわる必要もないため、次のように記述することもできます。
<div class="searchResult">
<div class="searchResult-item">...</div>
</div>
どのようなセパレーターを使う場合でも、サイト内で統一されていれば問題ありません。
BEMのメリットとデメリット
BEMのメリットとデメリットについてまとめておきます。
メリット
BEMの公式ドキュメントには、デザイナー、フロントエンドエンジニア、プログラマーなどの、プロジェクトに関わるチームメンバー全員がBEMを理解することで、次のようなメリットがあると述べられています。
- HTMLとCSSは、いつでもデザインの変化に対応できる
- プログラマーとフロントエンドエンジニアは、お互いのコードに貢献し合える
- 皆が同じ言語でやりとりできるので、コミュニケーションが取りやすくなる
額面通りのメリットを実際に享受できるかどうかは、BEMの考え方を理解し、それぞれの立場で、間違いなくコードに反映できるかにかかっています。
特に、Modifierという存在は、かなり概念的なものであるため、実際にModifierをどのように使うかは、チームメンバー内で意思の疎通が必要かもしれません。
デメリット
筆者としては考えられるデメリットとして、次のような点があると考えます。
- 独特な記法のため、BEMを知らない人から見ると、違和感を持つ人もいる
- ElementやModifierの名前が冗長になりがち
独特な記法に対する違和感ついては、デメリットという大げさなものではないかもしれません。従来のCSSを書き慣れている人が、BEMを使い始めると、感じるかもしれないことです。
名前が冗長になりがちな点に関しては、Blockの名前をできるだけコンパクトにするしかないですが、単語を省略しすぎると意味が伝わらなくなり、そちらの方が問題があるため、バランスを見ながらある程度は割り切りましょう。
まとめ
こういったHTMLとCSSの設計手法は、昔からいろいろと存在し、開発者それぞれが各自、似た考えを持って同じような設計*をしていたりします。しかし、そこまで具体的に系統だてられてルール化されている例は多くないと思われます。
*注:CSSの設計
ちなみにピクセルグリッドでも、BEMと似たような考え方でCSSを設計、運用しています。興味のある方は次のシリーズを参照してみてください。
- 「CSSの設計」シリーズ
今回は、BEMの概要をお伝えするために、公式のドキュメントに書かれている内容を中心に解説しました。次回は、具体的なCSSの設計方法や、JavaScriptの扱い、Blockのカプセル化の考え方などについて掘り下げて紹介する予定です。