pixivのCSSで使われるクラス名ルールを読んで、僕もここ最近 CSS のコンポーネント設計について似たようなことを考えていたので書いてみる。いまのところ試案で、実際のプロジェクトで実践したことはまだない。

ここでいう「コンポーネント」とは独立したスタイルのブロックの意味で、たとえばヘッダーのナビゲーションとか画像のスライダーとかブログ記事のボディとかを指す。このアプローチの狙いとしては前出の記事とほぼ同じで、これらコンポーネントの「ルート」を明確にし、コンポーネント名が衝突しないようにすること。また管理や共有を考え、特殊な命名規則や不自然なマークアップなどはなるべく避けたいというのもある。Sass などの CSS プリプロセッサーを使う前提だけど、なくてもある程度は使える。

まずマークアップでは、コンポーネントのルートとなる要素に、クラス名と同時に data-component という属性を指定する。コンポーネントのルートもその子孫要素も、クラス名に特別な命名規則は設けない。

<div class="imageSlider" data-component>
    <div class="viewport"> ... </div>
    <div class="nav"> ... </div>
</div>

CSS セレクターはつねにコンポーネントをルートにして書き、かつクラス名だけではなく、必ず [data-component] という属性セレクターも指定する。

// _imageSlider.scss
.imageSlider[data-component] {
    overflow: hidden;
    ...

    .viewport { ... }
    .nav { ... }
}

そして、すべてのコンポーネントをそれぞれ個別のパーシャルとし、ファイル名はそのコンポーネントのクラス名とする。たとえば .imageSlider のスタイルは _imageSlider.scss というファイルに定義する。これらのパーシャルを同一ディレクトリに格納すれば、コンポーネントを追加するときにコンポーネント名の衝突を防ぐことができる。

// main.scss
@import "component/heroUnit";
@import "component/imageSlider";
@import ...

コンポーネント名の衝突さえ防げれば、コンポーネント内のセレクターとして要素型 (ul とか em とか) や「素朴な」クラス名 (.title とか .description とか) も使いやすくなる。これらコンポーネント内のセレクターの衝突まで完璧に防ぐのは難しいけど、これは :not() 擬似クラスと > 結合子を使えばできなくはない。

.imageSlider[data-compnent] :not([data-component]) ul,
.imageSlider[data-compnent] > ul {
    list-style-type: none;
    ...
}

こう書けば子孫コンポーネントへの望まない継承を避けられるけど、さすがにすべてのコンポーネントに対してここまでやるのはちょっとやりすぎの感がある。入れ子になる可能性のあるコンポーネントについて個別に対応した方がいいかもしれない。

僕は CSS が壊れやすいものということは承知しているつもりだけど、かといってカスケーディングと継承を忌避したりするようなことはしたくないと思っている。ふつうのセレクターを使ってふつうの CSS が書きたい。またマークアップは簡潔にしたいという気持ちも依然として強いし、class 属性に過剰な役割を追わせるのも気が進まない。というようなことを考えながら、ここらへんが落としどころとして良さそうなのではないかと思いはじめているのがこのアプローチ。