Bootstrap と BEM を組み合わせた CSS 設計パターンについて考える

前置き - CSS 設計が難しい件について

誤解を恐れずに言うならば、CSS は変数も関数も条件分岐もない、ある種ゆるふわな言語(仕様)といえます。そのためプログラミング言語のように記述ミス一つで全ての挙動が止まるなんてことはありませんし、いくら冗長に記述しようがブラウザ上での挙動に差異が生まれることも殆どありません。ちょっと嗜めばそれっぽいものが作れてしまうので、マークアップエンジニアのいない小規模体制の組織であれば、サーバーサイドエンジニアやデザイナーが片手間で習得して実装してしまうというのも珍しいことではないでしょう。それでも良かったのかもしれません。これまでは…。

片手間で学習した知識というのはなかなか体系化されないものです。CSS も御多分に漏れずプログラミングのテクノロジーは日進月歩なため、その時は最新だった技術が僅か一年も経たないうちに廃れてしまい、バッドノウハウ化してしまうのは日常茶飯事です。実装担当者が一人ならまだしも複数人となるとそれぞれ異なる知識で CSS を書いてしまい、記述に統一性のないカオスなコードの一丁上がりとなりがちです。勢いに任せて作りきってしまい、あとからのメンテナンスで大いに後悔したことある人は僕だけではないはず。

CSS に限らずプログラミング / コーディングで大切なのは、

PREDICTABLE 予測しやすい
MAINTAINABLE 保守しやすい
REUSABLE 再利用しやすい
SCALABLE 拡張しやすい

これらの原則に則ることです。一発作りきりのランディングページであればまだしも、保守運用・拡張が前提となる Web サービスであれば、属人性を極力排したコードを維持することが大事なわけです。

CSS 設計パターンを考える上で命名規則は不可欠

上に挙げた四原則にある PREDICTABLE はまさに命名規則の重要性を示しています。僕がコーディングをする上で一番頭を悩ませるのが変数や関数、そしてCSS のクラス名といった命名な訳です。仮に .foo, .bar, .baz といった命名でも正常に動くことは出来ます。クラス名に .myouji とか .namae みたいなローマ字表現をしても動作自体に何の支障もありません。.text1.mainContentSubOuter などクラス名に連番を振ったりいかにも後から付け足したような命名であっても動くことは動きます1)まぁそんなコード見つけたら問答無用で締め上げて罵詈雑言を浴びせる訳ですけどねぇ…

CSS のクラス名はその名前からどんな用途複数人のプロジェクトであれば勿論のこと、一人プロジェクトであっても後から混乱しないように予めきちんとした命名規則を決めておくのはものすごく大事なことなのです。

BEM - CSS 命名規則のデファクトスタンダード

cap-bem

Block, Element, Modifier の頭文字をとって BEM です。フロントエンド設計において命名規則やモジュールの管理において、前述の3種類の区分を取り入れたものを言います。

Block
構成のルートとなる要素。各種コンポーネントはこの単位で作られるものとする。
Element
Blockに所属する子要素。必ず Block の中でのみ存在し、単体では生きられない。
Modifier
元となる Block または Element から変化した状態を表す要素。

BEM についての詳しい解説はここでは割愛しますが、あまり良く知らないという方は以下のリンク先が非常に分かりやすいので読んでおくことをオススメします。

Bootstrap - CSS フレームワークのデファクトスタンダード

cap-bootstrap

Bootstrap のスゴイところはそれまで曖昧に命名されていた UI コンポーネントに適切な名前とルールを認知させたところにあります。ボタンっぽい見た目であれば .btn というクラス名といったものから、.navbar, pagination, .pager, breadcrumbs などいかにも命名が揺れそうなコンポーネント郡に対して皆が納得できるような名前を付けてくれたのは非常に大きな貢献であると思います。例えば Bootstrap におけるパンくずリストのクラスはbreadcrumbs ですが、topic-path という命名もあり得るわけです。topic-path がパンくずリストにふさわしくないということではないのですが、Bootsrap が浸透したことで長いものに巻かれろ精神の元、breadcrumbs にしておけば、命名揺れのリスクを抑えることが出来ます。つまり一度でもBootstrapを使ってUIを組んだことのある人であれば、そういった認識のズレが起きるリスクやコーディングルールを決めるコストを劇的に減らすことができるんですよね。

Bootstrap の命名規則

Bootstrap は OOCSS という設計に基いています。例えばボタンをスタイリングするには.btn というベースとなるクラスを付与し、更に.btn-primarybtn-danger といった装飾クラスを組み合わせることでボタンの用途に合わせた色をスタイリングすることが出来るようになっています。さらに.btn-sm.btn-lg.btn-block といったボタンのサイズだけが定義されたクラスを組み合わせることでバリエーションに富んだボタンのスタイルを適用することが出来ます。ここでのボタンの例は BEM で言うところの Modifiers に近いものがあります。一つのスタイルを作るのに複数のクラスを組み合わせて指定することから、マルチクラスと呼ぶこともあります。

.btn {
    display: inline-block;
    padding: 6px 12px;
    margin-bottom: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    background-image: none;
    border: 1px solid transparent;
    border-radius: 4px
}
.btn-default {
    color: #333;
    background-color: #fff;
    border-color: #ccc
}
.btn-primary {
    color: #fff;
    background-color: #337ab7;
    border-color: #2e6da4
}
.btn-success {
    color: #fff;
    background-color: #5cb85c;
    border-color: #4cae4c
}
.btn-lg,.btn-group-lg>.btn {
    padding: 10px 16px;
    font-size: 18px;
    line-height: 1.33;
    border-radius: 6px
}
.btn-sm,.btn-group-sm>.btn {
    padding: 5px 10px;
    font-size: 12px;
    line-height: 1.5;
    border-radius: 3px
}
.btn-xs,.btn-group-xs>.btn {
    padding: 1px 5px;
    font-size: 12px;
    line-height: 1.5;
    border-radius: 3px
}
.btn-block {
    display: block;
    width: 100%
}
<button type="button" class="btn btn-default">Default</button>
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-primary btn-lg">Large button</button>
<button type="button" class="btn btn-primary btn-sm">Small button</button>

しかし厳密に言えば、これは BEM の Modifiers とは似て非なるものです。BEM であればワードの区切りと Modifiers の区切りを区別するために __ とアンダースコア2つを区切りに使うべきですが、Bootstrap ではワードの区切りも全て - とハイフン一つだけに統一されています。そもそも Bootstrap には Block と Element との区別が明確になされていません。つまり BEM よりももっと曖昧な命名規則で作られているのです。

【本題】 Bootstrap をベースにしつつ BEM を取り入れることは出来ないのか?

Bootstrapはあくまでフレームワークであり、フロントエンドを美しく構築するための基盤でしかありません。したがって Ruby on Rails の Scaffold だけで実用的な Web アプリケーションが作られることがないように、Bootstrap のコンポーネントだけで完結させて,コーダーが一行も CSS を書くことなく Web サービスをつくり上げることはまず無いと考えるべきです。

つまり殆どの開発現場では Bootstrap をカスタマイズすることで最適な UI デザインに仕上げていくことになります。当然 Bootstrap が用意しているコンポーネントではカバーしきれないユニークなデザインのコンポーネントも出てくるでしょう。その場合はプログラマーが独自に CSS を追記して自前で独自コンポーネントを開発しなくてはなりません。

ここでようやく本題に入ります。自前コンポーネントはどのような命名規則に則るべきでしょうか?ここまで紹介したように、Bootstrap は BEM の規則に則っていません。したがって Bootstrap がベースになっている以上、自作コンポーネントを BEM で命名すると一つの Web システム内に2つの命名規則が混在してしまうことになります。それだけは絶対に避けなくてはなりません。

ここでいうこところのカスタマイズには大きく三通りの方法が考えられます。

  1. Bootstrap 既存のコンポーネントのコードを直接編集する
  2. Bootstrap 既存のコンポーネントをベースにしつつ新しい Modifiers を追加する
  3. Bootstrap に依存しない全く独立したコンポーネントを自作する

1. Bootstrap 既存のコンポーネントのコードを直接編集する

殆ど既存コンポーネントで間に合うんだけど、余白やサイズ等を微妙に調整したいときに行う方法です。ボタンの角丸のアール調整や navbar や list-group など既存コンポーネントの余白調整といった小規模なカスタマイズはこの方法で対応します。

他には、使うことが想定されないコンポーネントやスタイルをコメントアウトしてファイルサイズの削減を図る時などでしょうか。

2. Bootstrap 既存のコンポーネントをベースにしつつ新しいModifiersを追加する

.btn-primary.btn-danger といった既存には無い Modifiers を追加する時や、既存コンポーネントを継承して新しいコンポーネントを作るなど Bootstrap に依存した拡張をするときに行う方法です。Bootstrap が読み込まれている事が前提になっている以上、Bootstrap の一部として考える為、命名規則も Bootstrapと同じ全て - 区切りで命名します。またサイズに関する Modifier に関しては、 small サイズなら -sm、large サイズなら -lg などといった表記も既存のそれに合わせて省略したのもを命名します。

3. Bootstrap に依存しない全く独立したコンポーネントを自作する

ベースに出来そうな既存コンポーネントが無い場合は、開き直ってフルスクラッチで作るのが一番の近道です。この場合は Bootstrap が無くても単独で成立するコンポーネントとして作成するので、BEM で命名します。

シングルクラスではなくマルチクラスを採用する

Sass 3.3 以上であれば、以下のように書くことで、シングルクラスにすることが出来ます。

.block {
  width: 100%;
  @at-root {
    #{&}__element {
      background-color: blue;
      margin-left: 10px;
      width: 25%;
    }
    #{&}__element--modifier {
      @extend .block__element;
      background-color: red;
    }
    #{&}--modifier {
      @extend .block;
      margin-top: 20px;
    }
    #{&}--modifier__element {
      @extend .block__element;
      background-color: green;
    }
  }
}

コンパイル結果は以下のとおり。

.block, .block--modifier {
  width: 100%;
}
.block__element, .block__element--modifier, .block--modifier__element {
  background-color: blue;
  margin-left: 10px;
  width: 25%;
}
.block__element--modifier {
  background-color: red;
}
.block--modifier {
  margin-top: 20px;
}
.block--modifier__element {
  background-color: green;
}

シングルクラスの利点は、HTML 側で指定するクラスが一つで事足りるのでコード量が少なくシンプルになる、デザイン崩れのリスクを抑えることが出来るというのがあげられますが、デザインのパターンの柔軟性が乏しくなるという欠点も持ち合わせます。マルチクラスは柔軟性が高いという利点がありますが、HTML 側で指定するクラスが多くなるのと指定ミスでデザインが崩れるという欠点もあります。

どちらを選択するかは当事者が決めることですが、Bootstrap がマルチクラスを採用しているのと個人的な好みから、僕は BEM もマルチクラスに寄せることにしています。

.block {
  width: 100%;
  .block__element {
    background-color: blue;
    margin-left: 10px;
    width: 25%;
    &.block__element--modifier {
      background-color: red;
    }
  }
  &.block--modifier {
    margin-top: 20px;
    .block--modifier__element {
      background-color: green;
    }
  }
}

コンパイル結果は以下のとおり。

.block {
  width: 100%;
}
.block .block__element {
  background-color: blue;
  margin-left: 10px;
  width: 25%;
}
.block .block__element.block__element--modifier {
  background-color: red;
}
.block.block--modifier {
  margin-top: 20px;
}
.block.block--modifier .block--modifier__element {
  background-color: green;
}

おわりに

各種プロジェクトのフロントエンドを構築するにあたって Bootstrap をカスタマイズするというのは前々からやっていたことなのですが、どうしても既存のコンポーネントとの住み分けが上手く行かず、可読性のよろしくないオレオレコードに成り下がってしまうことが続いていました。これではダメだと何度もリファクタリングを試みるのですが、Bootstrap の原型を保とうとすればデザイン通りの UI を再現することができず、再現しようとすると Bootstrap の原型が崩れていったり、不毛なパッチコードが積み重なっていったりと泥沼の限りを繰り返してきました。

今回ご紹介した設計であれば、Bootstrap の原型を極力保ちつつデザイン通りの UI を再現することができるようになります。Bootstrap と BEM とで命名規則が2つにはなるものの、それぞれの規則で記述される CSS の対象は明確に分離されるので可読性は保たれ、後からプロジェクトチームに参加するメンバーもそれほど苦労することなくコードを把握することが出来るかと思います。

どんなに多くの人が貢献したとしても、どのコードも一人で書いたようにする

All code in any code-base should look like a single person typed it, even when many people are contributing to it.

Idiomatic CSS

脚注

脚注
1 まぁそんなコード見つけたら問答無用で締め上げて罵詈雑言を浴びせる訳ですけどねぇ…