読者です 読者をやめる 読者になる 読者になる

実践 めんどうくさくない BEM

この記事は BEM Advent Calendar 2013 の12日めの記事です。

BEM は優れた方法論だと思うが、大変めんどうくさいことを強いてくることがある。この記事ではそんなめんどうくさい BEM を、少しでもめんどうくさくない BEM に変えられるかどうかを思索するものである。なお、めんどうくさくなくする過程で「それは既に BEM ではな」くなっている面もあると思うが、そこは承知の上なので念頭に置かれたし。

CSS セレクターにタグを書くのは本当にダメなのか

例えば以下のコードがある。

<section class="item-list">
  <h1>アイテム一覧</h1>
  <ul>
    <li> ... </li>
    <li> ... </li>
    <li> ... </li>
  </ul>
</section>

上記のコードはシンプルなので、各要素にスタイルを当てるとしたらこのような CSS で十分と思われる。

.item-list { ... }
.item-list h1 { ... }
.item-list ul { ... }
.item-list li { ... }

しかし BEM 原理主義では、上記のような CSS は書いてはいけないことになっている。つまり、CSS セレクタにはタグ名を決して書いてはいけないのだ。代わりにこうしなくてはならない。

<section class="item-list">
  <h1 class="item-list__heading">アイテム一覧</h1>
  <ul class="item-list__items">
    <li class="item-list__item"> ... </li>
    <li class="item-list__item"> ... </li>
    <li class="item-list__item"> ... </li>
  </ul>
</section>
.item-list { ... }
.item-list .item-list__heading { ... }
.item-list .item-list__items { ... }
.item-list .item-list__item { ... }

めんどうくさっ! こうしなければいけない理由はちゃんとある。ブロックの独立性を確実に担保するためである。独立性がしっかり担保できていないとどうなるか。たとえば、この .item-list が別のブロックを含むことになった場合、別ブロックの中に h1 があると、それは .item-list h1 のスタイルを意図せず継承してしまうことになる。

<style>
.item-list { ... }
.item-list h1 { color: red }
.item-list ul { ... }
.item-list li { ... }

.hoge-block { ... }
.hoge-block h1 { font-size: 150% }
</style>

<section class="item-list">
  <h1>アイテム一覧</h1>
  <ul>
    <li> ... </li>
  </ul>

  <section class="hoge-block">
    <h1>ほげ見出し</h1>
    <p> ... </p>
  </section>
</section>

BEM の思想では各ブロックが完全に独立していることが求められる。上記コードの .hoge-block は、 .item-list の中にいる時と、外にいる時とで h1 のスタイルに違いが出てしまう。どういうことかというと、 .item-list の外に配置されている時は単にフォントの大きな見出しだが、 .item-list の中に入った途端に赤くなってしまう。これでは .item-list は文脈に依存していることになり、独立性があるとは呼べない。

なるほど独立性を確保するためになら、少々冗長でもエレメントのクラス名を割り振ることは仕方がないのかもしれない。

……本当にそうだろうか? 自分は例外を認めてもよいと思っている。

  • クラス名がなくてもセレクターで文脈が固定できる場合
  • 要素構造的にそれ以外あり得ない場合

ul 要素の子要素は li 要素であると決まっている。では、このようにしてはどうか?

<style>
.item-list { ... }
.item-list__heading { color: red }
.item-list__items { ... }
.item-list__items > li { ... }
</style>

<section class="item-list">
  <h1 class="item-list__heading">アイテム一覧</h1>
  <ul class="item-list__items">
    <li> ... </li>
    <li> ... </li>
  </ul>
</section>

li のクラス名は無くとも、子セレクタを使うことで影響範囲を発散させることなくスタイルを指定することができた。僕はこれで十分だと思っている。

「じゃあ、.item-list__itemsul じゃなくなったらどうするの?」という疑問はもっともかもしれないが、その指摘は重箱だなあと思う。

「そのような機会は稀だし、万が一あったとしたら一瞬で CSS を書き換えればいい」

もし BEM を文脈非依存に加えて HTML タグにさえ非依存であることに期待しているのだとしたら、そもそも間違えていると思う。たとえば ulblockquote に書き換えられたらどうするのか。UA のデフォルトスタイルをどのみち上書きしなきゃーならんでしょ、という。とはいえ HTML5 な現代なので、見出しタグをセレクタに用いるのは避けたほうがよいだろう。見出しタグを相互に書き換え可能にしておくため。

例外の2つ目は、要素構造的にそれ以外あり得ない場合はエレメントのクラス割り当ては省略しても構わないのではないか。.item-list が内包するものはひとつの見出しと一連のリストのみ。他のブロックを内包することは無い。と決まっていた場合、もはやセレクタは最初の一番シンプルなもので十分だろう。

.item-list { ... }
.item-list .item-list__heading { ... }
.item-list ul { ... }
.item-list li { ... }

.item-list が他のブロックを内包することは無いと決まっていた場合、ここに無理してクラス名を付与することはない。

BEM では各ブロックをどこに移動させても同じように問題なく動くように作るように強いてきているが、現実的にはそうする必要は無いことが多いし、各ブロックには“他のブロックを内包しうるかどうか”みたいなものがあるはずだ。

エレメントのエレメント

Twitter のタイムライン上で BEM の話題が出ているときに見かけた覚えがあるので言及しておく。エレメントがさらにエレメントを持つ場合はどのようにすべきか? クラス名で書くなら block-name__elem-name__elem2_name とするのか? どんどんクラス名が長くなってしまうという懸念がある。

これは単に、エレメントのエレメントは作らない、という決めをしてしまえば良い。先述の例でいうと、 .item-list__items の子供である li 要素のクラス名は、 item-list__items__item でなくてよい。 item-list__item で十分だ。もしあまりに深い階層の要素までブロックに対する直接のエレメントとすることに気がとがめる場合、適当なところでブロックを区切ればよい。ブロックの中にブロックが内包されている形に整理するということ。

大抵の場合、ブロックの区切り(とあるブロックにブロックが内包されていた時に、内側のブロックが外に出ても同じ形を保っているべきかどうか)を判断するのはマークアッパーだ。

キモい BEM 的クラス名をなんとかする

block-name__elem-name--modifier と書かせる記法は完全にキモい。慣れの問題という話でもあるが、多くのマークアッパーはキモさを感じたことがあるはず。しかし頑張って慣れる必要は必ずしもない。一貫した規則と機械可読性さえあれば各区切り文字は変えたり消したりしても良いことになっている(本当です)。ならばこうしよう。

blockName-elemName_modifier

こうなる理由です。単語間のハイフンは単純に文字数の無駄である。単語間にハイフンを使うって決めてしまうことで、エレメントやモディファイアの区切りの記号に2文字使わなくてはならなくなるなんて MOTTAINAI 。キャメルケースで大文字も大いに使おう。クラス名に大文字を使うことに抵抗があれば、それは単なる思い込みでしょう。ハイフンとアンダースコアは逆でもいいと思うけど、使用頻度はエレメント>モディファイアであるから、より打ちやすいハイフンをエレメントとの区切りとした。

ちなみにモディファイアは2単語以上から成るが、それも stateCurrenttypeB のようにキャメルケースを使ってつなげればよい。

汎用クラスは使っても良いのか

アドホックにここだけマージンを変えたいとか、文字を中央寄せにしたいみたいなニーズに対し、 .mt10 みたいなクラスを付与することがある。そのスタイルの内訳は .mt10 { margin-top: 10px !important } みたいな感じだ。こういうクラスを汎用クラスと呼ぶことにする。汎用クラスは便利なものだが、BEM では使うことは想定されていない。文書構造と関係ないクラスを付与するという観点から忌避するマークアッパーも多い。

文書構造うんぬんには触れないことにするが、BEM 原理主義がどうであろうと、使ってもいいと自分は思う。ただし汎用クラスが濫用される危険性があるのは BEM に限った話ではないので、節度を守って使うべきだ。

個人的には余り使う場面はない(なくせる)と思っている。BEM のモディファイアを使えば多くはカバーできるはず。ページによって固有のマージンを持たせなきゃいけない場合などは使い勝手がいいと思う。ページ専用の CSS ファイルを持つのは面倒だし、ページ個別の id 属性を起点に文脈を特定する手法は見通しが悪くなる。どちらかというと、class="mt10" よりは style="margin-top: 10px !important" のほうが素直で好感が持てるけれども。

まとめ

BEM ではブロックに内包されるすべての要素(タグ)はブロックに対するエレメントである必要がある。しかし条件が整っていればエレメントにしなくてもいいかもしれない。

エレメントのなかのエレメントは、その構成を頑張ってクラス名で表現する必要はない。どちらもブロックに対する直接のエレメントと捉えれば十分だ。

BEM のクラス名ルールは、一貫性があって機械的にエレメントとモディファイアなどの切り分けが可能なルールがあれば自由に決めていい。キモいクラス名を見ずに済む。

汎用クラスは、使う必要があれば使えばいいと思う。

まとめ2(BEM に対する雑感)

BEM は日本で流行の兆しを見せてきたのはここ数ヶ月のことだが、似たような方法論は昔からたくさんある。僕が以前に勤めていたキノトロープという会社では、エレメントとコンポーネントという言葉をつかって近い方法論を実現していた。これは開発現場の手法というよりむしろ情報設計の単位として取り扱われていて、同じ概念を実装にも落とし込められるようにと、開発手法として BEM に似たものを先人がこしらえていたものだ。そういう意味でも BEM の本来の狙いと近かった。それなりの規模のウェブサイトを手がけている制作会社ならば、どこでも似たような手法は持っていたのではないかと思う。

この記事でまとめたようなユルさを BEM に適用してゆくと、結局既存の方法論と大差がなくなってくる。BEM の無駄な部分やコスト対効果の低い部分を削ると、あれ、そんなに良いものではないのか? という気がしてくる。

とはいえ、ブロックごとにスコープを絞ってコーディングするとか、可搬性が高まるように設計することは非常に良いプラクティスであることには違いない。これまであまり馴染みがなかった人は、まずは BEM を写経して体に覚えさせるのは全然アリだろう。

最後にひとつ BEM をディスっておこうと思う。BEM を考案した Yandex という企業は同名のポータルサイトを立ち上げている。BEM はこのポータルサイトで使うことを想定して作られた方法論だが、Yandex は実際見てみると分かる通り、画面はかなりシンプルな構成で機能も薄い。心なしかデザイン的な詰めも甘い。要するに、大層な方法論のわりに実際に使われた実績がしょぼい気がするのだ。日本のウェブサイトは得てして画面構成が詰まっていて多機能だ。そういう環境で BEM は十分にテストされているとは言いがたい。BEM 、あまり信頼し振り回されると痛い目を見るかもね。

大規模開発に耐えうる・メンテナブルな HTML 記法に、自分は以前から高い関心を抱いていたが、いまだにコレといった答えを出せずにいる。これからも飽きずに自分なりの手法を磨いてゆこうと思う。

このあたりのテーマをまさに中心に近い位置に据えてこれまで仕事をしてこられたであろう、弊社ネコメシ社長の春日井 COO からも一家言いただきたいと思っている。開設されたものの更新のない社長ブログ、更新されないかな~。

あしたは kubosho_ さんです。