CSS
Rails
CSS3
scss
bem

一番詳しいCSS設計規則BEMのマニュアル

More than 1 year has passed since last update.

一番詳しい(当社比)

この記事は

大体1年くらいBEMを実践した中で溜まった知見的なものをルール・規則・注意点をまとめたマニュアルというかたちにしたもの.
BEM初心者でもすぐ実践してもらえそうな感じに詳しくしたつもり.
ちなみにBEMの実践というのはRails製Webアプリでの実践.
※注 多分に我流な部分も含まれている. この記事の全てを真似しようとせずに一部のエッセンスのみ取り入れるのもいいかもしれない.

BEMとは

Block Element Modifierの略で,
Block => でかい括り
Element => でかい括りの中にいる要素
Modifier => 上記2つの変化球
の3つに分けて考えていくことでCSSを設計・命名していく手法.

基本的な考えとか前提とか

  • BEMの中でも特にMindBEMdingと呼ばれている命名法をベースとしている
  • SCSSを使用する
  • 基本的にはシングルクラスで設計したい
  • HTMLの構造とSCSSの構造は極力分離したい

命名

block__element--modifier のように, blockとelementはアンダースコア2つで区切り, elementとmodifierはハイフン2つで区切る.
blockが複数単語になる場合は, 単語と単語の間はハイフン1つで区切る. element, modifierが複数単語になる場合も同様である.

例えばarticle-listというblockの中にarticle-titleというelementがある場合, このelementのクラス名は article-list__article-title となる.

ファイル

SCSSファイルは1blockにつき1ファイルで作成する. 逆に言うと1ファイル内に複数のblockが定義されているのは違反である.
ファイル名は block名.scss とする.

上記の article-list blockの例で言うと, このblockが定義されているファイルは article-list.scss となる.

CSSは全ての名前がグローバルなため, 命名が重複するとメンテナンス性が著しく低下するのが弱点であるが, BEMは命名規則を厳しく縛ることによってこの弱点を克服しており, element名はいくら重複しても問題ない.
ただし, block名が重複してしまうとせっかく克服したものが台無しになってしまうため絶対に避けなければならない.
1ファイルにつき1blockしか定義せずファイル名をblock名にする規則を守ってさえいれば, block名が重複する心配が無い.

SCSS

以下のSCSSの機能を用いたルールを設定する.

  • elementはネストと親セレクタ名参照を使って定義する
    • element内にさらにelementをネストして定義してはいけない
  • modifierはネストと親セレクタ名参照を使って定義する
    • modifierはplaceholder selectorを使って差分のみ記述する

elementはネストと親セレクタ名参照を使って定義する

例えば article-list__article-title を定義すると以下のようになる.

.article-list {
  width: 100%;

  &__article-title {
    font-size: 20px;
  }
}

element内にさらにelementをネストして定義してはいけない

仮にHTMLが以下の構造になっているとする.

<div class="article-list">
  <div class="article-list__article-title">
    タイトル
    <span class="article-list__article-subtitle">サブタイトル</span>
  </div>
</div>

この場合に, HTMLではネストしているからといって以下のようにarticle-subtitleを定義するのは違反である.

.article-list {
  width: 100%;

  &__article-title {
    font-size: 20px;

    &__article-subtitle {
      font-size: 16px;
    }
  }
}

(そもそもこの例だとHTMLのほうでクラス名を間違っているのでspanには意図したスタイルは適用されないが)
article-subtitleはHTMLの構造ではarticle-titleの子なので, SCSSの構造でもarticle-titleの子であるようにネストして書きたくなるが, このSCSS構造を許すとHTMLの構造とSCSSの構造を分離できなくなる.

HTMLとSCSSの分離を目指すため, HTMLの構造がどうであれ, elementがblock以外を親に持つのを避け, 以下のように書く.

.article-list {
  width: 100%;

  &__article-title {
    font-size: 20px;
  }

  &__article-subtitle {
    font-size: 16px;
  }
}

modifierはネストと親セレクタ名参照を使って定義する

例えばarticle-titleには文字色が赤いものとそうでないものがあるとき, article-titleのmodifierをネストして定義する.

.article-list {
  width: 100%;

  &__article-title {
    font-size: 20px;

    &--red {
      // この中身がどうなるかは後述. もちろんこのままでは何も機能しない.
    }
  }

  &__article-subtitle {
    font-size: 16px;
  }
}

この場合例えばHTMLはこうなる.

<div class="article-list">
  <div class="article-list__article-title">
    赤くないタイトル
    <span class="article-list__article-subtitle">サブタイトル</span>
  </div>

  <div class="article-list__article-title--red">
    赤いタイトル
    <span class="article-list__article-subtitle">サブタイトル</span>
  </div>
</div>

modifierはplaceholder selectorを使って差分のみ記述する

引き続いて上記の例で説明する.
仮に, article-list__article-title--red は文字色が赤いだけで, その他のスタイルは全て article-list__article-title と共通であるとすると以下のようになる.

.article-list {
  width: 100%;

  &__article-title {
    font-size: 20px;

    &--red {
      font-size: 20px;
      color: red; 
    }
  }

  &__article-subtitle {
    font-size: 16px;
  }
}

この例では共通部分は font-size: 20px; だけなので問題ないように見えるが, 共通部分がさらに増えた場合, article-list__article-title--redarticle-list__article-title の共通部分の整合性を保ちつつ管理するのは大変面倒である.
modifierが --red だけならまだしも --green--blue が登場するとさらに地獄になる.

これを避けるため共通部分は以下のようにplaceholder selectorを使って一箇所にまとめ, modifierにはelementに対する差分のみを記述すればいいようにする.

.article-list {
  width: 100%;

  %__article-title {
    font-size: 20px;
  }

  &__article-title {
    @extend %__article-title;

    &--red {
      @extend %__article-title;
      color: red; 
    }
  }

  &__article-subtitle {
    font-size: 16px;
  }
}

--green--blue が登場しても以下のように簡潔である.

.article-list {
  width: 100%;

  %__article-title {
    font-size: 20px;
  }

  &__article-title {
    @extend %__article-title;

    &--red {
      @extend %__article-title;
      color: red; 
    }

    &--green {
      @extend %__article-title;
      color: green; 
    }

    &--blue {
      @extend %__article-title;
      color: blue; 
    }
  }

  &__article-subtitle {
    font-size: 16px;
  }
}

HTML

HTMLにも以下のように気をつける点がある.

  • blockの子としてblockが登場してもよい
  • blockの無いところにelementが登場してはいけない
  • シングルクラスで記述する

blockの子としてblockが登場してもよい

(むしろこのルール無いと書けないでしょという感じだが)
例えばカテゴリ別に記事が並んでいるようなページを考えると以下のようになる.

<div class="category-list">
  <div class="category-list__category-title">
    カテゴリ1
  </div>
  <div class="article-list">
    <div class="article-list__article-title">
      タイトル
    </div>
    <div class="article-list__article-title">
      タイトル
    </div>
  </div>

  <div class="category-list__category-title">
    カテゴリ2
  </div>
  <div class="article-list">
    <div class="article-list__article-title">
      タイトル
    </div>
    <div class="article-list__article-title">
      タイトル
    </div>
  </div>
</div>

category-list blockの子要素として article-list blockが登場しているが, これは全く問題ない.
そしてSCSSは1ファイルに1blockであるため, category-listarticle-list は互いにネストすることもなく完全に独立して定義する.

blockの無いところにelementが登場してはいけない

このようなSCSSがあるとする.

.hoge-block {
  width: 5000px;

  &__link {
    color: blue;
    cursor: pointer;
    text-decoration: underline;
  }
}

このとき, hoge-block の子ではない場所で hoge-block__link を指定してはいけない.

<div class="article-list">
  <!- これは違反である! ->
  <a class="hoge-block__link">
    記事へのリンク
  </a>
</div>

BEMの長所の1つとして, 「SCSSのとある箇所がどの範囲のHTMLに影響を及ぼしているか」を予測しやすいという点があるが, 例に上げたような書き方を許してしまうとその長所を殺し, hoge-block__link のメンテナンスコストが爆増する.
そのため, hoge-block__link が登場してよいのは hoge-block の範囲内だけである.

「とはいってもこのサイト全体でリンクのスタイルは統一したいんだけど」というニーズはいくつか発生するはずである. これの対処は下記のTipsの block横断で共通にしたいスタイルは専用ファイルに切り出す に記載した.

シングルクラスで記述する

基本的に全てシングルクラスで記述する. 後述のTipsでは例外的にマルチクラスを許容する場合の説明をするが, それ以外の場合ではマルチクラス指定は禁止する.

例えば上記で違反の例として使った以下のHTMLを考える.

<div class="article-list">
  <a class="hoge-block__link">
    記事へのリンク
  </a>
</div>

この違反状態を解決するために以下のように書くのもマルチクラスになってしまっているため違反である.

<div class="article-list hoge-block">
  <a class="hoge-block__link">
    記事へのリンク
  </a>
</div>

シングルクラスの話題とは少しずれるが, 上記のマルチクラス違反を回避しようとして以下のように書くのも避けるべきである.

<div class="hoge-block">
  <div class="article-list">
    <a class="hoge-block__link">
      記事へのリンク
    </a>
  </div>
</div>

hoge-block の範囲内で hoge-block__link が登場しているぶんには違反ではない.
しかし, article-list の範囲内に hoge-block__link が登場してしまっている状態は解消されない.
これもまた長い目で見ればSCSSのメンテナンスコストを増大させる.

Tips

少々細かいものを紹介する.

  • blockの単位と大きさにこだわらない
  • blockのmodifierは使わない
  • blockにはmarginを指定しない
    • blockとelementのマルチクラスのみ許容する
  • block横断で共通にしたいスタイルは専用ファイルに切り出す

blockの単位と大きさにこだわらない

HTMLの構造を無視してSCSSを作り, HTML的にはblockの子としてblockが存在しても構わないという条件だと, どういう単位・大きさでblockを作っていけばいいかは設計者の気分に委ねられている.
このマニュアルではblockの大きさは下記2つの判断基準でしか決めない.

  1. 1ページ(または1箇所)のみでなく, 複数箇所で使うことになるUI要素があれば, それをblockとする
  2. 上記の基準で判断できない場合は1blockが大きくなりすぎないように大雑把にblockを決める

要するに適当である.
特にBEM初心者であれば, どこからどこまでを1つのblockとし, どこから別のblockとして定義していくか困ると思うが, 困ったら適当で構わない.

ここまでで説明したようなルールに従って記述されたSCSSとHTMLでは, 大きい1つのblockを小さい2つのblockに分割するような変更はかなり容易なはずである.
容易であればこそ, 再利用性が高いと判断できないものについては無理に適切な大きさになるようにblockを細かく作ろうとせず, ちょっと大きすぎるくらいでblockを作っておき, 「この部分は再利用したほうがよさそうだ」と判断できた時点でリファクタリングすればよい.

blockのmodifierは使わない

BEMの本来の規則ではblockのmodifierは存在してよいことになっているはずだが, このマニュアルではこれを禁止している.
理由はそもそもそんなもの必要ないということと, SCSSでいい具合に書く方法が見いだせていないということである.

細かくは説明しないが, blockのmodifierを用意するとなると考えなければならないことがすごく増える. このマニュアルは2倍くらいの量になりそうだし, SCSSも複雑化する.
なにより, blockのmodifierを使えなくとも十分な表現が可能なので必要ない.

blockにはmarginを指定しない

気をつけるべきはほぼmarginのみの場合が多いが, margin以外にも要素の外側に影響を及ぼすようなスタイルは指定するべきでない.

例えばSNSのユーザーページのように, 友人のリスト・写真のリスト・投稿のリストなどのいろいろなリストが並ぶページを考える.

<div class="user-page">
  <div class="friend-list">
    <!-  ->
  </div>
  <div class="image-list">
    <!-  ->
  </div>
  <div class="post-list">
    <!-  ->
  </div>
</div>

このとき, それぞれのリスト同士は10pxずつmarginをとるとした場合に以下のように書くのは避けるべきである.

ほんとは各ブロック別ファイルになる
.user-page {
}

.friend-list {
  margin-bottom: 10px;
}

.image-list {
  margin-bottom: 10px;
}

.post-list {
  margin-bottom: 10px;
}

前述の blockの単位と大きさにこだわらない で軽く触れたが, blockは一箇所だけでなく複数箇所で使われうるということを念頭に置くべきである.
friend-list blockの margin-bottom: 10px; の指定は, ユーザーページでfriend-listが表示されることを前提にした指定であり, ユーザーページ以外でfriend-listを表示したい場合に邪魔になりかねないものである.

したがって, blockへのmargin指定はblockの汎用性を下げるため指定するべきでない.

blockとelementのマルチクラスのみ許容する

「でもblockにmargin指定しないとユーザーページの各リストが10pxのマージンとれないでしょ」という場合に対処するため, blockとelementが指定されるマルチクラスのみ許容することにする.
具体的には以下のようにする.

<div class="user-page">
  <div class="user-page__list friend-list">
    <!-  ->
  </div>
  <div class="user-page__list image-list">
    <!-  ->
  </div>
  <div class="user-page__list post-list">
    <!-  ->
  </div>
</div>
ほんとは各ブロック別ファイルになる
.user-page {
  &__list {
    margin-bottom: 10px;
  }
}

.friend-list {
}

.image-list {
}

.post-list {
}

このように書くことで, 各blockの汎用性を保ちつつmarginは実現できる.
前述の通り, これ以外の場合のマルチクラス指定はするべきではない.

block横断で共通にしたいスタイルは専用ファイルに切り出す

例えばサイト全体でリンクのスタイルは統一したい場合, blockそれぞれにリンクとなるelementをコピペして定義せざるを得ないため, メンテナンス性もクソもない状態になる.

この場合は, block横断で共通となるファイルを別に用意し, その中にplaceholder selectorでリンクのスタイルを定義しておく.
そのファイルを各blockでimportして必要な箇所でextendする.

global.scss
%link {
  cursor: pointer;
  text-decoration: underline;
}
article-list.scss
@import "global";

.article-list {
  &__link {
    @extend %link;
    font-size: 16px;
  }
}

blockそれぞれにリンクとなるelementを定義しなければならない点ではやや冗長だが, こうすることで全blockで完全に同一のリンクのスタイルを用意することも, 一部のblockで少しだけ変更したリンクのスタイルを用意することも可能である.

まとめ

長々説明したが要するにコンポーネント指向みたいな話である
そのうちたぶん加筆修正します.