細かすぎるけど伝わってほしい私的BEMプラクティス30(ぐらい)

BEMのいいところは、それが何者なのかが明白ということに尽きる。とある要素を見たときに、そのスタイルがどこに書かれているのか、何を表しているのかがクラス名を見ればわかる。手を入れる際も、どこに追記すればよいのか、どれくらいの影響を及ぼすのかの大部分が推測できる。

レスポンシブ・デザインと相性がいいとか、流行りのコンポーネント指向と相性がいいなど、BEMの良さは他にもいくつか挙げられるけど、決定的なのは明瞭さであると思う。

BEMを使いはじめてかれこれ3,4年くらい経った。その間に色々な命名規則や設計思想が登場してきたけれども、今のところは浮気する程の魅力を他に感じることもなくBEM一筋でやってきている。ただし実践するにつけて、より明瞭で破綻しづらい設計を実現するために、様々な制約やガイドを設けてやってきたので、「もともとのBEM」からは多少なり離れているかもしれない。

ただし、それはBEM的にはオッケーなのである。もともと「正しいBEMの書き方」というものはなく、みんなの中にそれぞれのBEMがある、という捉え方でオッケーなのだということだ(みたいなことが実は公式にも書いてある)。

少し長くなるけれど、私がいつも使っているプラクティスを紹介する。

目次

  • クラス命名規則系
    • 大文字のブロック名
    • アンダースコア2つでエレメント
    • エレメントの入れ子は避ける
    • 名前空間を利用しない
    • ハイフン始まりのモディファイア
    • 状態とバリエーションを区別しない
    • 単独モディファイアに対しスタイルを宣言しない
    • ユーティリティクラスを許容しない
    • むやみに抽象化をしない
    • 3タイプのブロック
    • レイアウトブロックと通常ブロックを分けない
    • エレメント名に迷ったら __1 __2
    • 端的な名称
    • むやみに単語を省略しない
  • CSSの書き方系
    • SCSSを使う
    • ブロックごとのファイル分割
    • ひとつのファイルの構成
    • &__element の書き方をしない
    • ブロックの中の要素にまとめてスタイルを当てない
    • @extend は使用しない
    • @import はアルファベット順
    • スタイルを当てる主体に注目する(ブロック内ブロック編)
    • スタイルを当てる主体に注目する(モディファイア編)
  • HTMLの書き方系
    • ブロックの中のブロック
    • div span が増えることを恐れない
    • 繰り返し現れるブロックを囲む要素を設ける

クラス命名規則系

大文字のブロック名

<div class="BlockName">
  <div class="BlockName__elementName"></div>
</div>

ブロック名にはアッパーキャメルケースを使う。ブロックであることが明瞭に認識でき、ブロック名とエレメント名の区切りとのメリハリもつき読みやすい。またサードパーティ製のCSSフレームワークの多くとも被らないので事実上、名前空間が不要になる。

アンダースコア2つでエレメント

エレメント名の前にはアンダースコアを2つ並べる。1つでない大きな理由があるわけではないが、2つ並べる作法はMindBEMdingというメジャーなBEM命名規則として広まっているため、それを踏襲している。

エレメントの入れ子は避ける

.BlockName__element1__element2 { ... } /* NG */
.BlockName__element2 { ... } /* OK */

前者のような書き方はしない。この書き方は公式でも非推奨とされている(参考)。HTML構造上の入れ子関係があっても、常にブロック直下のエレメントであるように書く。

名前空間を利用しない

.top-BlockName {...}
.BlockName {...} /* Better */

名前空間はあまり推奨しない。

名前空間を使うモチベーションとしては、サードパーティ製のCSSフレームワークやライブラリとのクラス名バッティングの問題や、巨大なサイトでブロック名が枯渇する問題などがある。

しかしよっぽど大規模なウェブサイトでない限り、必要になることはない。名前空間を採用しないことでうれしい副作用がある。重複しないような名前付けを余儀なくされるため、具体的かつ明確な命名が上手になる。

仮に名前空間を採用するとなった場合、名前空間にまつわる政治活動が必要になってくる。その名前空間は誰がどのようなタイミングで増やすのか、あるいは削除するのか、複数の名前空間にまたがるようなブロックはどう取り扱うのか、などなど。

ハイフン始まりのモディファイア

<div class="BlockName -opened"></div>

モディファイアにいちいちブロックのクラス名を付けるのは単純に面倒くさい。ブロック名を省略してハイフン始まりのクラス名を付与する。ブロック名をつける場合と比べて、一瞥してモディファイアと識別でき、短く、JSとの相性もよい。

ハイフン始まりのクラス名は実は普通に使える。正確には、HTMLのクラス名にはどんな文字列を入れても構わない一方、CSSセレクタとしてクラスを指定するときには色々と制約があるのだが、ハイフン始まりのクラス名セレクタは不正ではない。ただし、先頭にハイフン2つが続くのはダメなので、ハイフンひとつ始まりになっている。

指摘されがちな問題点として、ひとつのHTML要素に複数のブロックやエレメントが割り当てられていた場合に、モディファイアがどちらに係るのか分からなくなる! という指摘がある。しかしこれはたいてい問題にならない。たとえばブロックが「-opened」という状態であれば、普通はエレメントも同時に「開いている」状態であり、わざわざブロック名のついたモディファイアを別々に割り当てる必要はない。

状態とバリエーションを区別しない

<div class="BlockName -typeLarge -typeBordered -isOpened"></div>

<!-- Better -->
<div class="BlockName -large -bordered -opened"></div>

ブロックやエレメントの「状態」と、単なる見た目の「バリエーション」とを別々のモディファイアの書き方とする流派がある(例示は「状態」を is 、「バリエーション」を type でプレフィックスしている)。しかし、この2つを明確に区別して考えられる人は稀である。もともと分けない方がいい。

単独モディファイアに対しスタイルを宣言しない

.-opened { display: block; } /* NG */
.BlockName.-opened { display: block; } /* OK */

モディファイアをユーティリティクラスのように使ってはいけない。モディファイア付きのスタイルは、必ずブロック/エレメントと一緒に宣言する。

ユーティリティクラスを許容しない

上マージンを10pxに設定するために -mt10 と書くようなやり方は許容しない。これをするぐらいなら、style="margin-top:10px" と書いたほうがマシである。

むやみに抽象化をしない

「割り当てるスタイルが共通している」くらいの理由でブロックを作ってしまうのは予測できないCSSを生み出す第一歩。たとえば「左に画像がFloatして右側にテキストが来る」パターンに共通性を見出して「Media」ブロックを作ってしまうのは勇み足といえる。あまりに頻出パターンなのでHTMLのあらゆる箇所にMediaブロックが氾濫することになり、すぐに破綻してしまう。ブロックのくくりは「大きめ」に取ることが原則。

一般にプログラミング言語には、共通した処理をまとめて記述する手段が用意されており、同じことを何度も書かないことを良しとするDRY原則というものがあるが、CSSでは逆に、あえて重複を許容することで複雑化に歯止めをかけられることがよくある。

3タイプのブロック

ブロックはその性質に応じて3タイプに分ける。「第一級ブロック」「通常ブロック」「ページブロック」。

第一級ブロックは、粒度が細かくサイトの中で頻繁に登場する。ほかのブロックの中に内包されて登場することも多い。ボタンやアイコン、“ などのフォーム入力欄あたりが該当する。第一級ブロックはそんなに数は多くないはずで、あっても10通りくらい? という印象。

ページブロックは、ページ(テンプレート)ごとにスタイルの微調整をかけるためのブロック。ページ全体をひとつのブロックと見なす。問い合わせ入力ページならば、InquiryInputPage みたいな名前で、body要素のclass属性値に割り当てられるのを想像してもらえばよい。ページブロックは微調整が必要になったときのための保険みたいなもので、できるだけ使わずに済むに越したことはない。

それ以外は通常ブロック。ほとんどのブロックは通常ブロックであり、第一級ブロックやページブロックはそこからはみ出た例外だと考える。

レイアウトブロックと通常ブロックを分けない

「レイアウト用のブロックに l- プレフィックスを付ける」というようなルールは無い方がよい。すべて通常ブロックに寄せる。なにがレイアウトで、何がレイアウトでないかは、微妙なケースがあり、線引きが人によって異なるなど難しいことが多い。また、プレフィックスを見て「これはレイアウト用ブロックだ」ということが分かっても、あまりうれしいことがない。

エレメント名に迷ったら __1 __2

<div class="BlockName">
  <div class="BlockName__1"></div>
  <div class="BlockName__2"></div>
</div>

明瞭な名前をつけるに越したことはないが、難しい場合は __1, __2 みたいなエレメント名でもオッケー。

端的な名称

ブロック名をつけるときは、必ず「そのブロックは何を表しているのか?」を具体的に言葉で説明できるようにし、それをそのままブロック名に落とし込む。つまり最初に日本語で説明してみることを心がける。「TextBox」「CenterBox」のような曖昧模糊とした名前付けは避けること。「ProductDetail」「CarouselA」「PasswordGenerator」など、その「モノ」を表す特徴的な単語を含めて名付けるようにする。デザイナーやクライアントとの間で共通言語になっている単語があれば、積極的に採用する。

むやみに単語を省略しない

わかりにくくなる一方でメリットがない。省略で節約できた数キロバイトは、たとえば画像を適切に圧縮するなどの簡単な工夫で節約できるバイト数に比べたら微々たるもの。

CSSの書き方系

SCSSを使う

LESSやStylusは遠く見えなくなってしまいました……。

ブロックごとのファイル分割

1ブロック1ファイルに分ける。面倒臭がらずに必ずやる。怠けると「どこに何が書いてあるかわからない」コードが簡単にできあがってしまう。

ひとつのファイルの構成

@charset "UTF-8";

.BlockName {
  @at-root {
    $_localVariable: 10px;

    & { ... }

    .BlockName__element1 { ... }
    .BlockName__element2 { ... }
 }
}

という感じに書く。ローカル変数のためのスコープを作るため、ブロック全体を .BlockName {...} で囲み、@at-root を入れ子にすることで、エレメント用セレクタの詳細度を小さく保つ。

ローカル変数名はアンダースコアから始める。ベタだけどやっぱりわかりやすい。

&__element の書き方をしない

SCSSの機能で、親のセレクタを使って &__element というような書き方ができるが、使用しない。制作時におけるデメリットが山ほどあるからだ。例えば、セレクタによる検索ができなくなる。エディタによるリファクタリング機能が効かなくなる。使用箇所の一覧、定義箇所の参照ができなくなる、など。

もっとも、親セレクタとの関係性を表すために & を使うのは問題ない。

.Block1` {
  & + .Block2 {...} /* OK */
}

ブロックの中の要素にまとめてスタイルを当てない

.BlockName {
  ul, li, p { margin: 0; padding: 0; }
}

みたいな書き方は、しない。これらul, li, pにはきっとエレメント名が割り当てられ、エレメントとしてのスタイルも設定されるはずだが、上記の書き方だと詳細度の逆転が起きてしまい(.BlockName ul よりも .BlockName__element の詳細度のほうが低い)、予期せぬハマりを呼びやすい。

@extend は使用しない

使いこなせば強力だが、使いこなせる人は少数なので使わない。使う場合は、意図や使用上の注意を記したコメントを大目に残すようにする。

@import はアルファベット順

記述順に依存したスタイルの指定はできる限り避けるべきで、その実践としてブロック名のアルファベット順に読み込むという習慣は悪くない。

ただし、先述の第一級ブロックは、ほかの多数のブロックに内包される機会が多いため、先に読み込んだほうがよい。第一級ブロックのアルファベット順→通常ブロックのアルファベット順→ページブロックのアルファベット順、に読み込むのがよい。

スタイルを当てる主体に注目する(ブロック内ブロック編)

1ブロック=1ファイルとした上で、_hoge-block.scss というファイルには「HogeBlockを主体とするスタイルを記述する」のが原則。

次のようなケースを考える:「FugaBlockに内包されたときのHogeBlockのスタイルを指定する」。このとき、.FugaBlock .HogeBlock {...} というセレクタは、どちらのファイルに書くのが適切だろうか。

多くの場合 _hoge_block.scss の中に書くのが適切となる。そのとき _hoge_block.scss の中身を上から読み上げていくと、「HogeBlockは基本こんなスタイル。ただしFugaBlockに内包されたときに限り、スタイルちょっと変える」というふうになる。_hoge_block.scss は、HogeBlockに関する原則と例外を含むが、あくまでHogeBlockについての(を主体とした)スタイルを宣言しているという点が重要。

難しいですね?

スタイルを当てる主体に注目する(モディファイア編)

  • HogeBlockブロックがある
  • HogeBlockは -opened モディファイアをとる
  • -opened のとき、HogeBlockやHogeBlock__elementのスタイルが変わる

これをどのようにコード化したらよいだろうか。

// 案1
.HogeBlock {
  @at-root {
    & { color: red; }
    &.-opened { color: blue; }

    .HogeBlock__element { display: none; }
    &.-opened .HogeBlock__element { display: block; }
  }
}
// 案2
.HogeBlock {
  @at-root {
    & { color: red; }
    .HogeBlock__element { display: none; }

    &.-opened {
      & { color: blue; }
      .HogeBlock__element { display: block; }
    }
  }
}

案1と案2、どちらを採用するべきかは、実際のところ場合による。しかし理由をはっきりさせたうえで選択するとコード見通しが良くなるので、ひと呼吸おいて考えるようにする。ポイントは、「モディファイアがつくことによって、そのブロックがどのように変化するか」の捉え方である。

案1は、「基本はこのスタイル、ただし -opened モディファイアが付いたときにはこうなる」というニュアンスを表現するのに向いている。

案2は、モディファイアの有無によって、ブロック内の様々なエレメントが見た目を変えるという性質を表現するのに向いている。

やっぱり難しいですね?

HTMLの書き方系

ブロックの中のブロック

ブロックの中に別のブロックを配置する場合、いくつかパターンが考えられる。

<!-- パターン1 -->
<div class="BlockA">
  <div class="BlockB">
    ...
  </div>
</div>

<!-- パターン2 -->
<div class="BlockA">
  <div class="BlockA__blockB BlockB">
    ...
  </div>
</div>

<!-- パターン3 -->
<div class="BlockA">
  <div class="BlockA__blockB">
    <div class="BlockB">
      ...
    </div>
  </div>
</div>

パターン3が最もよい。CSSの書き方が整理されるし、問題が起きても対処がしやすい。

div span が増えることを恐れない

本来使われるべきタグの代わりにdivタグやspanタグを使うのは忌避されるべきだが、divタグやspanタグそのものには罪はない。ちょっと難しいスタイルを実現するために、5重くらいdivで囲むくらいのことは臆せずやったほうがよい。divの数を一つでも減らしたいがために、不安定でわかりづらいスタイル指定をしてしまうことのほうが、避けるべき。

BEMにはあまり関係ない。

繰り返し現れるブロックを囲む要素を設ける

<!-- NG -->
<section class="ArticleList">
  <h2 class="ArticleList__heading">記事一覧</h2>
  <p class="ArticleList__sort">並び替え:<select>...</select></p>

  <article class="ArticleItem">...</article>
  <article class="ArticleItem">...</article>
  <article class="ArticleItem">...</article>

</section>

<!-- Good -->
<section class="ArticleList">
  <h2 class="ArticleList__heading">記事一覧</h2>
  <p class="ArticleList__sort">並び替え:<select>...</select></p>
  <div class="ArticleList__articles">

    <article class="ArticleItem">...</article>
    <article class="ArticleItem">...</article>
    <article class="ArticleItem">...</article>

  </div>
</section>

JSで制御するときや、テンプレート言語に組み込むときなど、繰り返しの親要素があったほうが何かと都合がいい。

BEMにはあまり関係ない。


26個だった。