Skip to content

Latest commit

 

History

History
628 lines (475 loc) · 24.6 KB

how-to-bem.md

File metadata and controls

628 lines (475 loc) · 24.6 KB

BEM(MindBEMding)によるCSS設計

BEMとは?

BEMYandexという主にロシア人で構成される、検索エンジンなどを作っている企業が使っているCSSの設計方法です。BEM Toolsという、jsonでデータを管理してBEMの規則に則ったHTMLを生成する仕組みによってサイトが管理されているようです。

BEMツールに触れてみる - < /gecko >

命名規則はBEMから派生したMindBEMdingの方が広く使われているので、MindBEMdingをベースに考えていきます。

MindBEMdingはcsswizardryことHarry Robertsが提唱したCSSの命名規則のことです。Harry RobertsはCSS界では世界的に有名で、コンサルタントやスピーカーとして世界中で活躍されています。


これ以降の内容は忠実なBEMの方法論ではなく、BEMの考え方にいくつかのアイデアを加えたものになります。
公式ドキュメントは日本語に翻訳されたbem-methodology-jaが公開されています。

BEMの概念

BEMはブロック(Block)、エレメント(Element)、モディファイア(Modifier)の頭文字をとったものです。
Blockはあるパーツ(コンポーネント)の親要素です。BlockはElementと呼ばれる子孫要素を持つことができます。Modifierはバリエーションや状態を変化させるときに指定するもので、BlockかElementと同階層に指定します。

命名規則はハイフン2つやアンダースコア2つでつなげます。

  • .Block
  • .Block__Element
  • .Block--Modifier
  • .Block__Element--Modifier

詳しい説明は後述します。

Block(親要素)

Blockはサイトを構成するパーツのことです。例えばボタン、グリッド、タブ、見出し、画像などで、簡単に言ってしまえばパーツの親要素です。BEMではBlockを起点として考え、サイトはBlockを組み合わせることで構成していきます。

例えばブロック要素で余白を持ったシンプルなBlockの.boxを作りました。

.box {
  display: block;
  padding: 1em;
}

ボタンもBlockとして考えることができます。ここではボタンのベースになるスタイルだけを持っています。

.button {
  display: inline-block;
  margin: 0;
  padding: 0.75em;
  border: none;
  border-radius: 3px;
  color: inherit;
  font-family: inherit;
  font-size: inherit;
  line-height: 1;
  text-align: center;
  text-decoration: none;
  background: transparent;
  cursor: pointer;
  appearance: none;
  &:hover,
  &:active,
  &:focus {
    text-decoration: none;
  }
  &:disabled,
  &.is-disabled {
    opacity: 0.5;
    pointer-events: none;
  }
}

2つのBlock(.box.button)を組み合わせて1つのパーツを作ることもできます。

<div class="box">
  <a href="#" class="button"></a>
</div>

Element(子孫要素)

ElementはBlockを構成するパーツの1つ1つのことです。例えばグリッドを構成するカラム、タブのボタンやコンテンツ部分、見出しや画像などです。

BEMではBlock(親要素)から見て下の階層にいる要素はすべてElement(子孫要素)として扱います。なので、Blockは1つしかありませんし、Elementは0個以上から構成されます。

クラス名は.block__elementのようにBlockの名前を引き継いでアンダースコア2つでつなぎます。

例えばグリッドレイアウトであれば、.gridがBlockになり、.grid__itemがElementになります。

.grid {
  display: block;
  margin: 0;
  padding: 0;
  font-size: 0;
  list-style-type: none;
}

.grid__item {
  display: inline-block;
  width: 100%;
  font-size: medium;
  font-size: 1rem;
  vertical-align: top;
}

先ほどの.boxというBlockのElementとしてボタンを定義しました。Block特有のスタイルがある場合はBlock__Elementとして定義します。

<!-- OK -->
<div class="box">
  <div class="box__button">
    <a href="#" class="box__button-item">button</a>
  </div>
</div>

ボタンのベーススタイルを持っている.buttonというBlockをマルチクラスで指定しました。.box__button-itemは色やサイズなどの装飾的な指定をするだけでよくなりました。

<!-- Good -->
<div class="box">
  <div class="box__button">
    <a href="#" class="button box__button-item">button</a>
  </div>
</div>

Modifier(バリエーション)

ModifierはBlockとElementのバリエーションや状態の変化をつくるときに指定します。

クラス名は.block__element--modifier.block--modifierのようにBlockとElementの名前を引き継ぎ、ハイフン2つでつなぎます。Modifier名は以下のようにつけるといいでしょう。

  • Block--small - あるBlockの余白やサイズなどが小さいバージョン
  • Block--pad-small - あるBlockのpaddingが小さいバージョン
  • Block--item-small - Block内のitemというElementが小さいバージョン

例えば.boxというBlockに背景色をつけたパターンが出てきたとします。

.box {
  display: block;
  padding: 1em;
}

Block自体ではなく、Blockに背景色を持たせるためのModifierを作ります。

.box--highlight {
  background-color: gray;
}

これで、元のBlockのスタイルを変更することなく、背景色をつけたパターンを作ることができました。HTMLはこのようにマルチクラスで指定することになります。

<div class="box box--heightlight">
</div>

基本的にリセットしていくよりも、足していくようにすると管理しやすくなります。

/* NG */
.box {
  display: block;
  padding: 1em;
  background-color: gray;
}

.box--no-highlight {
  background-color: transparent;
}

このパターンはOOCSSの原則のひとつである「構造と見た目の分離」と同じことをしています。つまり、BEMとOOCSSは組み合わせることもできます。

BEMのメリット

採用するメリットと導入コスト

BEMの概念や命名規則の方法など、導入するためのコストは必要です。ですが、BEMのルールは基本的に案件ごとに変わることはないので、導入コストは最初の案件だけに限られると思います。

また、逆にルールがあるということは、自分で考える必要がないということです。自分で考えるはずだった時間を、他の重要なことに使えます。後述する命名規則の明確さもメリットのひとつです。

基本的なルールが決まっていること、ルールを共通言語にすることでコミュニケーションを円滑にできること。BEMを採用するメリットはこのあたりにあるのかなと思います。

命名規則の明確さ

BEMを使うメリットのひとつにクラス名からクラスが持っている役割が伝わりやすいことがあります。

クラス名の単語をハイフンやアンダースコアで区切ると、

.widget-list {}
.widget_list {}

のようになり、ウィジェットリストなのか、ウィジェットの中にあるリストなのか、人によって解釈が変わってしまう恐れがあります。

BEMであれば、ウィジェットリストなら

.widget-list {}

になりますし、ウィジェットの中にあるリストなら、

.widget__list {}

のようにクラス名だけで判断することができます。


BootstrapやFoundationがハイフン区切りでも問題がないのは、

  • フレームワークとして完成した状態で使うことを前提としているのでブレない
  • ドキュメントが充実している

ことがあげられます。実際の案件ではじめからこの条件を満たすことはまずできません。いちから組んでいってもブレにくいルールが必要になります。

命名規則のアレンジ

BEMのクラス名はBlockの名前を引き継いだりハイフン2つやアンダースコア2つで区切ること、マルチクラス(複数のクラスを指定すること)を前提していることから、class属性の指定が長くなりがちです。

クラス名をなるべく短くするために、命名規則を案件ごとに微調整することがよくあります。命名規則には3つのパターンがあります。

1つ目、通常はblock-nameのように単語をハイフンで区切ります。

.block-name {}
.block-name__element {}
.block-name--modifier {}

2つ目、単語をローワーキャメルケースにした場合。

.blockName {}
.blockName__element {}
.blockName--modifier {}

3つ目、単語をローワーキャメルケースにして、区切り文字を1つにした場合。

.blockName {}
.blockName_element {}
.blockName-modifier {}

Elementが複数の単語からなっている場合はより短くできます。

.block-name__element-name {}
.blockName__elementName {} // 2文字少ない
.blockName_elementName {} // 3文字少ない

BEMの基本ルール

クラスセレクタ単体に指定する

BEMではIDセレクタや要素セレクタは使用せず、クラスセレクタで指定します。

/* Good */
.block-name {}
.block-name__element {}
.block-name--modifier {}

クラスセレクタ単体にスタイルを指定していくので、詳細度は0,0,1,0のままになります。!importantを使うこともほとんどありません。

IDセレクタを使わないのはページ内で使い回せるようにするためです。

/* NG */
#block {} // ページ内で1つしか使えない
#block__element {} // Elementが2つ以上ある場合に使えない
#block--modifier {}

要素を使わないのは影響範囲を限定させるためです。HTMLタグを限定させないことで使い回しをしやすくできるメリットもあります。

/* NG */
.block > p {} // Block直下の`<p>`すべてに適応されてしまう

案件によってルールを緩くした方が運用しやすいこともあります。どこまで厳密にするかは案件の規模、制作者の人数やレベル、運用をするひとのレベルなどから決めるといいでしょう。

ElementとModiferを単体で使用しない

「ここは.box__elementと同じデザインだから、.box__elementだけ持ってこよう。」と考えてはいけません。ElementとModifierは親となるBlockの中でだけ指定することができます。

<!-- NG -->
<div class="foo">
  <div class="box__element"></div>
  <div class="box__element"></div>
</div>

.box__elementが汎用的に使えるのであれば、.box__elementではなく、別のBlockとして定義しましょう。今は.box__elementは汎用的に使えるかもしれませんが、スタイルが変更される可能性もあります。

BEMのよくある問題

Element__Element

BEMでは必然的にElementの数が多くなります。Blockから見て孫要素ができることもあります。このときに.block__element__elementのようなクラス名にしているケースがあります。

<!-- NG -->
<div class="box">
  <div class="box__element">
    <div class="box__element__element">
    </div>
  </div>
</div>

BEMはHTMLの構造をElementによって表す必要はありません。child-elementのような名前にしても伝わりますし、より具体的です。

<!-- Good -->
<div class="box">
  <div class="box__element">
    <div class="box__child-element">
    </div>
  </div>
</div>

例えば多階層のナビゲーションをelement__elementでマークアップした場合、極端な例かもしれませんがこのように階層が深くなるごとに名前が長くなってしまいます。

<!-- NG -->
<ul class="nav">
  <li class="nav__item">
    <ul class="nav__item__items">
      <li class="nav__item__items__item"><a href="#" class="nav__item__items__link"></a></li>
      <li class="nav__items__items__item"><a href="#" class="nav__item__items__link"></a></li>
    </ul>
  </li>

  <li class="nav__item">
    <ul class="nav__item__items">
      <li class="nav__item__items__item"><a href="#" class="nav__item__items__link"></a></li>
      <li class="nav__items__items__item"><a href="#" class="nav__item__items__link"></a></li>
    </ul>
  </li>

</ul>

僕なら以下のようにマークアップします。.nav__itemsの子要素は.nav__child-itemsのようにchildをつけています。.nav__child-itemsの子要素は.nav__child-itemのようにsを抜いて名前をつけています。

<!-- Good -->
<ul class="nav">
  <li class="nav__items">
    <ul class="nav__child-items">
      <li class="nav__child-item"><a href="#" class="nav__child-link"></a></li>
      <li class="nav__child-item"><a href="#" class="nav__child-link"></a></li>
    </ul>
  </li>

  <li class="nav__items">
    <ul class="nav__child-items">
      <li class="nav__child-item"><a href="#" class="nav__child-link"></a></li>
      <li class="nav__child-item"><a href="#" class="nav__child-link"></a></li>
    </ul>
  </li>

</ul>

階層が深くなっても2つの単語で収めるようにできればコードが読みやすくなると思います。Blockという名前のスコープがあるので、意味が理解しやすい範囲でBlock内で重複しなければ十分です。

なるべく階層が深くならないようにBlockを組み合わせて使うようにすると、名前の付け方はシンプルになると思います。

例えば、上記のナビゲーションをラップする<nav>要素には.g-navのようなレイアウトを担当するクラス、スマホでハンバーガーボタンがあるなら.nav-toggleのようにBlockを分けて、組み合わせることで名前やコードが複雑になるのを避けることができます。

<!-- Good -->
<nav class="g-nav">
  <div class="g-nav__navigation">
    <ul class="nav">
      <li class="nav__items">
        <ul class="nav__child-items"></ul>
      </li>
    </ul>
  </div>

  <div class="g-nav__toggle">
    <div class="nav-toggle">
      <span class="nav-toggle__item"></span>
      <span class="nav-toggle__item"></span>
      <span class="nav-toggle__item"></span>
    </div>
  </div>
</nav>

Block__Element--Modifier

例えばElementのpaddingを変更するModifierを作りたい場合、すべてのElementにModifierを指定するのは冗長で指定忘れが出てしまったりするかもしれません。

<!-- NG -->
<div class="box">
  <div class="box__element box__element--pad-large"></div>
  <div class="box__element box__element--pad-large"></div>
</div>
.box__element {
  padding: 1em;
}

.box__element--pad-large {
  padding: 2em;
}

このパターンにはBlock--Modiferを起点にしてスタイルを指定する方が運用しやすくなります。直下の要素として指定したりして、影響範囲をできるだけ抑えるようにしておきます。

<!-- Good -->
<div class="box box--pad-large">
  <div class="box__element"></div>
  <div class="box__element"></div>
</div>
.box__element {
  padding: 1em;
}

.box--pad-large > .box__element {
  padding: 2em;
}

Blockを名前空間にする

BEMのルールではElementとModifierはBlockのなかでだけ存在することができるとあります。以下のHTMLはBlockが存在しないためルール違反になります。

<!-- NG -->
<div class="box--size-large">
  <div class="box__element"></div>
  <div class="box__element"></div>
</div>

セレクタの結合子にBlockを常に指定することでスタイルを適応できないようにすることもできます。

.block {}
.block > .block__element {}
.block.block--modifier {}
<!-- Blockがないので、スタイルは適応されない。 -->
<div class="box--size-large">
  <div class="box__element"></div>
  <div class="box__element"></div>
</div>

ただし、ここまで厳密にする必要はないと考えています。
セレクタを複雑にして厳密さを求めるより、BEMの基本的なルールを守ってもらえるようにコミュニケーションをとる方が必要ではないかなと考えています。

Sassの&(アンパサンド)

Sassでは入れ子と&(アンパサンド)を使ってBEMをショートカットで書くことができますが、BEMの命名規則の明確さを損なってしまうので、使わない方がベターだと思います。

/* Bad */
.foo {
  color: white;
  &--bar {
    color: red;
  }
  & .bar { // このセレクタは`.foo .bar`なのか?`.foo--bar .bar`?なのか?
    color: red;
  }
}
/* Good */
.foo {
  color: white;
}

.foo--bar {
  color: red;
}

.foo .bar {
  color: red;
}

検索しにくくなる(あるいはできなくなる)ことも使わない理由のひとつです。

/* Bad */
.foo {
  color: white;
  &--bar { // `.foo--bar`を検索しても引っかからない
    color: red;
  }
  & .bar { // `.foo .bar`を検索しても引っかからない
    color: red;
  }
}

Blockの粒度

Blockの大きさ(粒度)をどのように考えるかは、2つの基準があると考えています。

コンテナとコンテンツの分離(OOCSS)

OOCSSの「コンテナとコンテンツの分離」をできるようにしておきます。

コンテナとは.container.wrapperのような横幅を制限したり、グリッドレイアウトのような大枠のレイアウトを指定するものです。
コンテンツとは本文やリスト、画像やテーブルなどのパーツのことです。

このデザインはどんな構造(レイアウト)が必要なのか?その中にはどんなパーツがあるのかという順番で考えていくと作りやすく、使いまわしがしやすくなると思います。

どこまで汎用性を持たせるか

BlockにはいくつでもModifierを足していけますが、増えるごとに複雑さが増します。自分で定義したBlockならまだしも、誰かが定義したBlockであればModifierの組み合わせを把握しきれなくなる恐れがあります。

指定が複雑になってきてしまったら、別のBlockとして定義できないか考えましょう。「このパターンではこのBlockに2つModifierを指定してください。」よりも「このパターンではこのBlockを指定してください。」の方がシンプルで伝わりやすく、運用がしやすいと思います。

divが足りなくなってしまった

デザインの変更があり、コーディングをやり直す必要があるけどBlockを<div>でラップしないと難しいということもあります。そのときは、

  1. できればHTMLも含めて修正する
  2. Blockを組み合わせて対応できないか考える
  3. .block-wrapper.block-innerのようなクラスを作る

という順番で考えています。

<div class="block-wrapper"> 
  <div class="block">
    <div class="block__item"></div>
  </div>
</div>

.block-wrapperのようなクラスはBEMにはありませんが、Blockをラップするというイレギュラーに対応するためにこのようなクラスを作ることがあります。

BEM以外の手法との組み合わせ

BEMとBEM以外の設計手法を組み合わせることに問題はありません。例えば、

  • OOCSS
  • SMACSS
  • 汎用クラス(ユーティリティクラス)

などです。FLOCSSがいい例ですが、BEMだけですべてを解決することは難しいです。BEM以外の設計手法を柔軟に取り入れていくのがベターだと思います。

汎用クラス

BEMで汎用クラスを適切に書くことはできないと思います。もし書けたとしても冗長になるでしょう。

汎用クラスはBEMとは別のルールとして適応させます。例えば.display-none.dnのようなシングルクラスで適応できるようにします。

SMACSS

BEMにはバリエーションや状態の変化としてModifierがありますが、状態の変化はSMACSSのStateに任せてしまうのもありです。

.tab {}
.tab__trigger {}
.tab__content {}
.tab__trigger.is-active {}
.tab__content.is-open {}

Modifierはパターン違い、Stateは一時的な状態変化と分けて考えると把握しやすくなります。

リンク集

公式ドキュメント

BEM

OOCSS

EDJO

アイデア