CSSにおける汎用化の先送り、ユーティリティファーストCSS、レイアウトプリミティブ

CSSは普通、セレクタの記述から始まる。目の前にあるHTML片に対してどのようなスタイリングを施すかという前に、いかにしてそのHTML片を選択するかという意識が先に来る。あらかじめ完成したHTML文書へ向けてスタイルを適用していくのであればそれでうまくやれるのかもしれない。だが広く行われているウェブデザインの制作では、まずゴールとして定められた描画結果だけがあり、そこから逆算してHTMLとCSSを書き進めていく。つまり個別の結果だけがある状態で実装に取り掛かることになる。実装のために必要な構造化はたいてい後手に回る。

それでもCSSセレクタから始まることは変わらない。実装を進めるためにはまずセレクタを書かなければならない。セレクタは規則の根幹である。バグを減らし、開発を効率的にするためには、あらゆるスタイリングの意図をセレクタに反映させるのが基本だ。しかし最初から正確にその意図を把握できる機会はまれであり、現実には、無理矢理こじつけた妄想のような規則性を実装してしまう場合も少なくない。それが瞬く間にサイト全体に広まって取り返しがつかなくなることも。

これを回避するためには、コンポーネントなどに局所的な利用を明示するような名前最初はつけておくことだ。再利用性を念頭においたCSS設計では一般に、やや曖昧なコンテンツに依存しない命名が奨められるが、最初からそうするのは時期尚早だと経験上感じられる。まずは再利用性を制限するために、そのコンポーネントが利用される箇所やコンテンツの性質を積極的に反映させる。サイトのホームでニュース記事を表示させているカルーセルならば、「HomeNewsCarousel」のような冗長すぎる名前を選ぶのがむしろ良い。そして同じコンポーネントを別のページやコンテンツについても利用するのであれば、それがはっきりとわかってから、ふたたびその段階で判明しているコンテキストに応じてコンポーネントの名前をつけ直す。たとえばフィーチャーしたい記事をコンテンツ種別ごとの複数のカルーセルの繰り返しによってホームに表示するのであれば「HomeFeaturedCarousel」とか、別ページでもニュース記事を表示させるために使うのであれば「NewsCarousel」とか。

このように利用箇所に応じたコンテキストを明示し、変化があれば見直しの上で追従していく作業を、プロジェクトの生存期間中は半永久的に行う。利用するコードの意図がつねに明快になっている意味では健全だが、難点としてはただ、めんどくさい。特に開発の初期段階ではところ構わず再利用できた方が手数が少なくて楽な場合もある。しかし時に想定よりもはるかに長い期間メンテナンスされ続けるCSSにおいて、いかにすればこのめんどくささを軽減させて継続的な意図の反映を行なっていけるのか。

ユーティリティファーストCSS

ユーティリティーファースト(またはAtomic)CSSと呼ばれるアプローチがある。スタイル宣言と対応する細かなクラスがフレームワークとしてあらかじめひと通り用意されており、ユーザーは基本的には新たにCSSを書かずともHTML上でクラスを組み合わせていくだけでスタイリングが行えるというもの。

もっとも人気の実装であるTailwind CSSでは、たとえばチャットの通知アラートは、ユーザーが新しくCSSを記述しなくても次のHTMLだけで作ることができる。

<div class="max-w-sm mx-auto flex p-6 bg-white rounded-lg shadow-xl">
  <div class="flex-shrink-0">
    <img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">
  </div>
  <div class="ml-6 pt-1">
    <h4 class="text-xl text-gray-900 leading-tight">ChitChat</h4>
    <p class="text-base text-gray-600 leading-normal">You have a new message!</p>
  </div>
</div>

出典:Utility-First - Tailwind CSS

これを利用すれば初手にまず名前を考えるという作業はスキップできる。一見スタイルの再利用性の問題がありそうに思えるが、昨今のプロジェクトではなにかしらのテンプレートエンジンを採用するはずなので、テンプレート機能を用いてマークアップコンポーネント的に管理すれば解決できる。要点は後から共通化できることである。

インラインstyle属性との違いとしては、まずインラインstyle属性では利用できないメディアクエリや擬似クラスがユーティリティークラスとして用意されている点。そして次に各スタイル宣言の値が特定のバリエーションによって意図的に制約されるという点。単にインラインstyle属性を使うのでは、宣言の値は場当たり次第でユニークになってしまうことがある。余白やフォントサイズ、テキストの色など、これらの判断が宣言ごとにバラバラになっているとシステムとしての一貫性がなくなってしまう。

Tailwind CSSはあらかじめ決められた値のバリエーションと対応するユーティリティクラスだけを提供している。たとえば色についてはデフォルトのカラーパレットが設定されていて、カラーパレットにある値だけが色に関するプロパティと対応するユーティリティクラスになっている。提供されるクラスを使う限りはカラーパレットのルールを外れないというわけだ。

最初からこのフレームワークを利用してページをデザインするなら制約として機能するだろう。しかしそれを意識せずにすでにSketchなどのデザインツールでデザインされたページがあったとすれば、当然フレームワークの設定値(デザインシステムの文脈ではデザイントークンと呼ばれる)は意図に沿わない間違った制約になってしまう。ユーザーが任意の値によってデザイントークンを設定できるようになっていたとしても、やはり汎用化の話と同じく、最初から正しいデザイントークンを見つけ出すこと自体が困難だ。仕組み上、ほとんどのユーティリティクラスはデザイントークンとセットになっていないと存在できないので、ユーティリティファーストのアプローチは結果的に成り立たなくなってしまう。

さらにかなりの数が存在するユーティリティクラスの命名規則を覚える必要もある。長期的に付き合っていくプロジェクトではまだしも、そうでない場合に少し関わる程度のメンバーが毎度これに慣れるというのはそれなりの負担になる。

そしてこれはいわば低レイヤーのフレームワークであり、CSSフレームワークというよりはCSSを組み立てるためのフレームワークと表現した方が近い。既存のコンポーネントのようなものは用意されていないので、最初はすべてのものをユーザーが組み上げなければならない。

ユーティリティファーストCSSについてここまでで浮上した問題をまとめると、正しいデザイントークンの発見を前提にしないとアプローチが成り立たないことと、CSSの記述方法を代替する以上のものではないということだ。ではどうすればいいのか?

レイアウトプリミティブ

Every Layoutが提唱するレイアウトプリミティブは、頻繁に出現するレイアウトの最小要素を、レスポンシブデザインを前提としたCSSにおいても再利用可能にしたパターンのこと。

たとえば「The Stack」は、縦方向に繰り返す要素間に共通の余白を挿入するためのパターン。

<div class="Stack">
  <p>Lorem ipsum dolor sit amet consectetur.</p>
  <p>Lorem ipsum dolor sit amet consectetur.</p>
  <p>Lorem ipsum dolor sit amet consectetur.</p>
</div>
.Stack > * + * {
  margin-top: 1.5rem;
}

「The Center」は、要素の幅を特定のサイズを超えないように制限した上で中央に寄せるパターン。

<div class="Center">
  <p>Lorem ipsum dolor sit amet consectetur.</p>
  <p>Lorem ipsum dolor sit amet consectetur.</p>
  <p>Lorem ipsum dolor sit amet consectetur.</p>
</div>
.Center {
  max-width: 40rem;
  margin-right: auto;
  margin-left: auto;
  padding-right: 1rem;
  padding-left: 1rem;
}

このようなパターンが今のところ合計で12個紹介されている

ページには12のリンクがパターンを簡略化したアイコンとともに掲載されている

レイアウトプリミティブの特徴は、パターンの役割がとにかく純粋であること。責務を混合させずに独立させることによって、かなり広範囲の問題に対してパターンが適用できるようになっている。

それぞれのパターンは相互に組み合わせて利用する前提で設計されている。たとえばダイアログは次のような構成で実装できる。

ダイアログはCluster、Stack、Box、Centerの各レイアウトプリミティブで構成されている

登録フォームならこんな感じに。

3つのフィールドと送信ボタンを持つフォームはCenter、Stack、Box、Centerのレイアウトプリミティブで構成されている

あるいは講演でのスライド。

テキストが中央に配置され、前と次のボタンが下部に配置されたスライド。Cover、Box、Stack、Sidebarのプリミティブで構成されている。

いずれも出典は「Composition: Every Layout」より。

個人的な経験則として、レイアウトプリミティブのパターンは実際にかなり多くのレイアウトの実装に適用できる。それぞれのパターンをクラスとして再利用できるようにしておくと、結果的にCSSの総量をかなり削減できる。つまりはCSSを書く場面が減り、新しく作らなければならないコンポーネントや要素の数が減り、命名の機会が減る。もちろん共通化はテンプレートエンジンで行える。

パターンの収集という意味でレイアウトプリミティブは絶妙である。ウェブデザインの中で無意識的に繰り返されていたようなレイアウトの手法を拾い上げ、極めて汎用的な形式知に変換することによって、思いもしない抽象化の可能性が提示されたように感じた。OOCSSの原則であった「構造とスキンの分離」は、ページからそのパターンを発見する困難さゆえに機能しなかった。大袈裟かもしれないが、レイアウトプリミティブはウェブデザインの普遍的なパターンに思える。設計を進めていく最中でパターンを発見していくのには無理があり、あらかじめわかっているパターンを拠り所にできる方が間違いがないだろう。(レスポンシブデザインという制約が昨今のレイアウト規則を画一化した結果とも言えるかもしれない。)

しかし残念ながらEvery Layoutで紹介されている実装はそのままでは現実のプロジェクトには適用しづらい。特定の画面幅への最適化を避けて意図的にメディアクエリによるブレイクポイントに依存しない仕組みになっていたり、IE11で利用できない機能にしっかり依存していたり……。これらにはある程度納得できる理屈がありつつも、業務においても「そういうことで」とするにはかなり無理がある。ただそれでもこのアイデアはなんとか活用してみたかったので、1年近く苦心して、ある程度安定したプラクティスを見つけ出すことができた。それについては次の記事「実践的レイアウトプリミティブ」で紹介する。

参考文献