Tailwind CSSはCSS設計に何をもたらすか
5 月末頃に、Twitter で 「Tailwind CSS はプロジェクトにとって将来的に技術的負債になるかもしれない」という趣旨の意見を発端として Tailwind CSS について多くの議論が起きました。[1]
Tailwind CSS は世界的に人気な CSS フレームワークとして、今までも何回もその良さや課題について議論が起きてきましたが、「Tailwind CSS で得られる価値」について主張する意見において、CSS(や、CSS の関連技術)がなぜ技術的負債になるか、Tailwind CSS を使ったらなぜ CSS が技術的負債になることを回避できるかという視点から Tailwind CSS の特性を整理している意見や記事はインターネット上に多くないと思いました。この記事ではその点に注目しつつ、Tailwind CSS が CSS 設計にもたらす利点について整理したいと思います。(以後、技術的負債という言葉が長いので「負債」と略して表記します。)
この記事は
- なぜ CSS が技術的負債になるのか
- CSS 設計をするときに注意すべきことは何か
- Tailwind CSS が CSS 設計にもたらす利点は何か
という順で構成されています。1. なぜCSSが技術的負債になるのか」
が正直長ったらしいので、パパッと読みたい方は目次から『つまり、CSS を実装するワークフローまで含めて設計しなければいけない』に飛んで読んでください。
また、この記事では、CSS 設計を教義の意味の ①「BEM のような class を用いた CSS のコーディング規約の設計論」ではなく、広義の意味の ②「プロジェクトにおけるスタイル定義の開発生産性を維持するための技術選定とコーディング規約の設計」という意味合いで使います。
CSS が負債に変わるタイミングはいつか
CSS が負債になるタイミングはプロダクトの状況によります。まず、CSS が負債になる可能性のある状況を列挙し、それぞれを比較する中で何を重視すべきかを考えてみましょう。
そもそも技術的負債はいつ生まれるのか?
前提として、技術的負債というものはどんな状況であっても生まれ「プロジェクト・プロダクトにおける現在の技術的負債は何か」という視点の切り口の変化によってソフトウェアの開発における技術選定や設計、実装が技術的負債になる状況もあればならない状況もあります。
ソフトウェア設計における技術選定や設計の決定は、常に何かしらのトレードオフを伴います。そのため、完全に負債がない状況を作り出すことは難しいと言えます。
そのため Tailwind CSS のようなパッケージ化された技術によって CSS 設計の課題を解決しようとするときは、「どのような状況は避けたくて」「技術によって何の価値を得たいか」を明確にすることが重要でしょう。
スタイルが開発されてから配信されるまでのプロセス
CSS は、アプリケーション開発/Web 制作において開発プロセスの広い範囲で登場します。
以下にスタイルが開発されてから配信されるまでのプロセスを簡単にまとめました。
画像の内容をリストで読みたい方はこちらを開いてください
- プロダクトの CSS アーキテクチャの作成
- CSS 設計の検討
- コーディング規約の作成
- Reset CSS の選択
- スタイルを書くワークフローの整備
- デザイントークンなどのデザインシステムを用意する
- デザイナーによって Web で表現するのに適したコンポーネンタブルなデザインが組まれる
- デザインを CSS で実装する
- デザインを再現できるように HTML を構築する
- HTML に対してスタイルを割り当てる
- コーディング規約・CSS 設計に合わせて実装する
- デザイントークンを参照する
- CSS がバンドラーによってビルドされる
- Web サイトが配信される
大きく分けて「1. CSS アーキテクチャ設計」「2. デザイン」「3. 実装」「4. アセットビルド」「5. 配信」の 5 つのステップがスタイルの開発には関わっています。注目したいのは、「2.デザイン」から「3.実装」までが頻繁に繰り返しつつも成果物の品質が作業者に依存するので負債になる可能性をハンドリングしにくいステップということです。
CSS が負債に変わるタイミングと想定される負債
先ほどの開発プロセスにおいて CSS が負債に変わる例は以下のようなものが考えられます。
開発プロセスにおいて CSS が負債に変わる例
- アーキテクチャを作成するタイミング
- 適切ではない Reset CSS を選択してしまった
- デザインするタイミング
- CSS で表現するのに適したデザインになっていない
- コンポーネントとして表現するのに適したデザインになっていない
- color や spacing などのデザインに使われるトークンがそろっておらず、開発生産性を著しく低下させるデザインにしてしまった
- CSS を実装するタイミング
- デザインとは違う意図の方法で CSS を実装してしまった
- 変更が難しい・開発生産性を低下させる CSS を実装をしてしまった
- コーディング規約・CSS 設計を逸脱した実装をしてしまった
- アクセシビリティを配慮していない CSS を実装してしまった
- CSS をコンパイルするタイミング・CSS を配信するタイミング
- CSS を分割して配信することができず、アセットのサイズがページ表示速度のボトルネックになる
このように考えられる負債を列挙すると CSS に関わる負債は大きく分けて、「スタイルの開発生産性を低下させうるもの」と「ユーザーの体験を低下させうるもの」の 2 種類があることが分かります。
CSS を実装するタイミングの負債のインパクトが一番大きい
これらの負債のうち、CSS 設計では何を優先してハンドリングすべきなのでしょうか?
私は「スタイルの開発生産性を低下させうるもの」のうちCSS を実装するタイミングの負債をハンドリングすることが一番重要だと考えています。
なぜなら、CSS を実装するタイミングで生まれる負債は基本的にスタイルの保守性が失われている状態を生み出し、スタイルの保守性が失われている状態は別の負債を解消する際にブロッカーになるからです。
具体的には「このスタイルを修正すればあっちのスタイルが壊れる」といった状況を想像してみてください。
CSS を書いていてこのような経験をみなさん一度は経験したことがあると思います。
CSS の実装でよくあるツラい例
- id セレクタや
!important
によってむやみに高い詳細度や重要度でスタイルが書かれているために、適用されると思っているスタイルが適用されない - 同じ要素に対してむやみに上書きしてスタイルが定義されている・適用範囲の広いスタイルに依存していて、スタイルを修正すると思っている要素だけでなく他の要素にも影響が出てしまう
- モジュール(コンポーネント)がマージンを持っていて他に転用したときに既存のスタイルを修正することなく使えない
このような CSS の保守性が失われ既存のスタイルの影響範囲が分からない状況は、既存のデザインに破壊的な変更を加えずに負債を解消することが不可能な状態をまねきます。
(多くの場合、この問題の解決を狙うならばアプリケーションのスタイルを 1 から作り直す程度のコストを支払うはめになると思います。)
CSS の実装を負債にさせずに実装することは難しい
しかしながら、CSS の実装を負債にさせずに実装することは難しいです。
私はスタイルの保守性を失わないようにするには、CSS の実装において以下の 4 つの特性を保持する[2]必要があると考えています。
CSS の品質に関連する 4 つの特性
- 変更容易性:スタイルの変更が容易にできる
- モジュール性:閉じた影響範囲でスタイルを変更できる
- 再利用容易性:一度作成したスタイルを再利用できる
- 解析容易性:どのようなスタイルが実装されているのかわかりやすい
生の CSS や生 CSS 風にスタイルを記述できる API を持つライブラリを使いつつこれらの特性を保持することは難しいです。なぜなら、CSS は以下のような特性を持っているからです。
- スコープを持たない
- セレクターによってスタイルを適用する要素を指定する
- 複数のスタイルが記述されているうちから、cascading によって適応されるスタイルが計算される
- 同じスタイルを実装するにも様々な表現方法がある
とくに複数のスタイルが記述されているうちから、cascading によって適応されるスタイルが計算されるという特性は非常に厄介で、コーディング規約を統一できなければ詳細度が濫用されて「変更容易性」「解析容易性」はすぐに失われてしまいます。
セレクターを活用することで「モジュール性」「再利用容易性」を生み出そうとしますが、これも濫用されやすいです。
スコープを持たない問題を解決するために古くから BEM 等の CSS 設計や CSS in JS などの技術が生まれてきました。また、Chakra UI のようにスタイルを実装する API を持つコンポーネントを用いる例もあります。
BEM(CSS 設計)でスタイルを当てる例
<!DOCTYPE html>
<html lang="ja">
<head>
<style>
.card {
/* スタイル定義 */
}
.card__image {
/* スタイル定義 */
}
.card__content {
/* スタイル定義 */
}
.card__title {
/* スタイル定義 */
}
.card__text {
/* スタイル定義 */
}
.card__button {
/* スタイル定義 */
}
</style>
</head>
<body>
<div class="card">
<img class="card__image" />
<div class="card__content">
<h2 class="card__title">タイトル</h2>
<p class="card__text">テキスト</p>
</div>
<button class="card__button">ボタン</button>
</div>
</body>
</html>
emotion(CSS in JS)でスタイルを当てる例
import React from "react";
import { css } from "@emotion/css";
const styles = {
card: css`
/* スタイル定義 */
`,
image: css`
/* スタイル定義 */
`,
content: css`
/* スタイル定義 */
`,
title: css`
/* スタイル定義 */
`,
text: css`
/* スタイル定義 */
`,
button: css`
/* スタイル定義 */
`,
primaryButton: css`
/* スタイル定義 */
`,
};
export const Card = () => {
return (
<div className={styles.card}>
<img
className={styles.image}
src="image.jpg"
alt="A beautiful card image"
/>
<div className={styles.content}>
<h2 className={styles.title}>カード</h2>
<p className={styles.text}>テキスト</p>
</div>
<button className={[styles.button, styles.primaryButton]}>ボタン</button>
</div>
);
};
Chakra UI(スタイルを実装する API を持つコンポーネント)でスタイルを当てる例
import React from "react";
import { Box, Text, Button, Image } from "@chakra-ui/react";
export const Card = () => {
return (
<Box w="300px" boxShadow="lg" p="6" rounded="md" bg="white">
<Image borderRadius="md" src="image.jpg" alt="A beautiful card image" />
<Box pt="5">
<Text fontSize="xl" fontWeight="semibold">
カード
</Text>
<Text mt="2">テキスト</Text>
</Box>
<Button mt="5" colorScheme="teal" variant="solid" width="full">
ボタン
</Button>
</Box>
);
};
CSS の実装は開発者のスキルや判断に依存する
これらの技術はCSS の実装を負債にさせずに実装することは難しいという問題を解決できていません。
なぜなら、「再利用可能な適切なモジュールを作成するのは難しい」という問題や「早すぎた抽象化をしたことで変更容易性が失われる」などの開発者のスキルや判断に依存する問題に対して、これらの技術は解決策を提供していないからです。
具体的には、BEM のような class を用いた CSS 設計で考えてみましょう。
class を用いた CSS 設計でうまくいかない例
- BEM の命名規則を複数人で統一してつけられない
- BEM の class 名と文章構造への適用の仕方が実装箇所によって様々になってしまう
- 同じような命名のモジュールが複数作成されてしまった
- 様々な場所で使われるモジュールを作成したが、想定される文章構造(HTML)とは異なる文章構造でモジュールが使われた。元のモジュールに変更を加えたら、他の場所で使われているモジュールのデザインが崩れてしまった
- CSS の実装にデザイントークンを採用しようとコーティング規約で決めたのにスタイルの実装ではトークンが使われていない
- BEM の実装を Saas の mixin やネストセレクターなどを活用して実装したが、Saas の持つ機能の管理や把握が難しくなってしまった
class のみを使った CSS 設計は最近の開発において採用されることは少ないでしょうから、CSS in JS 等を使ったスコープを絞ってスタイルを実装する例で考えてみます。タグセレクターの使用によってスタイルの適用範囲が意図せず広くなっていたり、CSS 本来の柔軟な記述ができるがゆえにスタイルガイドに沿って実装を統一できない・CSS の記述方法についてのスタイルガイドを作成するほどのリソースがないという問題がでてきます。
このように、人がルールを判断して適応しなければいけない点は人によって判断が異なることが防げずに負債になってしまうでしょう。
CSS を実装する人は幅広い
さらには CSS を実装する開発者のスキルを担保しにくいという問題があります。
CSS はその実装の簡易さからさまざまなポジションの人によって実装されることが多いです。
例えば、デザイナーが CSS を実装している現場もあれば、サーバーサイドエンジニアが片手間で CSS を実装している現場もあるでしょう。[3]
ピアレビューを用いてスタイルガイドが適応されているかを担保している現場では、本当に全員のレビュワーが CSS 仕様とプロジェクトの CSS スタイルガイドに精通しているでしょうか?(そうだとしたらとても素晴らしいことだと思います。)
CSS in JS 等を使ったスタイルの実装では都度、ライブラリの API に合わせて書き方やライブラリ特性を学習しなければいけないという問題も生まれます。
CSS はしっかりしていても・・・
また、CSS を実装する開発者のスキルはあっても前段階のデザインが CSS として実装しやすいデザインになってなく、無理に実装しなければいけなかったりします。
デザイントークンの使用や繰り返された UI の使用など、デザインがコンポーネントとして表現しやすいデザインになっていなければ CSS を実装するときに再利用可能性を保つことは難しいなります。
残念ながらすべてのデザイナーがマークアップについて理解があるわけではありません。
ときには、Web で表現するのに適していないデザインを無理矢理実装しなければいけないタイミングもあります。
つまり、CSS を実装するワークフローまで含めて設計しなければいけない
CSS を実装するときには、ただスコープを閉じて CSS を書ける状況やスタイルガイドを提供しても CSS の保守性は担保できないことを説明してきました。
ではどうすればよいのでしょうか。私たちはCSS 設計をするときには、CSS を実装するワークフローまで含めて設計する必要があります。
繰り返しになりますが CSS は以下のようにいくらでも保守性が下がる危険性があります。
- コードの品質を人の目で確認しなければいけない
- レビューを行ってもパスしてしまう可能性がある
- CSS のスキルは人によってバラつきがありがち
- CSS 設計毎に学習する必要もある
- 少しでも実装に割れ窓ができればすぐに開発生産性、保守性が下がっていく
- 擬似的なスコープを作れる状況でもデザイントークンの不使用や不適切なセレクタの使用など保守性を下げる実装をする可能性はいくらでもある
なので、具体的に CSS を実装するときにどのような手順でデザインから CSS が実装され、その実装するワークフローの中で CSS の保守性が下がる危険性を回避するワークフローを全員が実行できるかまでを含めて設計しなければ CSS 設計で達成したい理想は得られません。
【本題】Tailwind CSS が CSS 設計にもたらす利点
私が思うにTailwind CSS は、表現方法が自由すぎる CSS に対して最適な実装方法をワークフローとして提供してくれるフレームワークです。
CSS 設計において Tailwind CSS を選べば、CSS の実装が負債になるのを回避するのによく練られたベストプラクティスをワークフロー込みで用意できます。
以下の機能によって、Tailwind CSS は CSS の実装における負債になるポイントを回避しながら、そのポイントを自然に実行できるようにワークフローに落とし込んでいます。
- ワークフローを使用している技術に依存せず用意できる
- デザインにデザインシステムが存在しない場合でもデザイントークンを管理して実装できる
- IDE 支援でスピーディーに実装できる
- スタイルに対して実装方法を一貫させた Opinioned な Utility class を提供している
- 高凝縮にすべきである文章構造とスタイル定義をコロケーションした実装をできる
- クラス名で詳細度を統一して実装できる
ワークフローを使用している技術に依存せず用意できる
CSS in JS 等では、そもそも JavaScript を用いたアプリケーションでないと使えないという問題があります。
Tailwind CSS はクラス名からスタイルを生成するという機能に終始しているので、Webpack(と postcss)などのバンドラーを用いてアセットをビルドしているプロジェクトであれば、どのような技術を使っていても Tailwind CSS を導入することができます。
また、「クラス名からスタイルを生成するという機能に終始している」ことで、従来の CSS フレームワークのように「そのフレームワークに依存する形でしかスタイルを実装できない・知識を使い回すことができない」ということもありません。
あとワークフローの実現に必要なのは、Tailwind CSS IntelliSenseだけです。
デザインシステムが存在しない場合でもデザイントークンを管理して実装できる
Tailwind CSS では、設定ファイルであるtailwindcss.config.js
を編集することで、デザイントークン(色やスペーシングに命名して管理すること)の値を CSS の実装するときに参照できるようにすることができます。
何が良いのかと言うと、柔軟かつわかりやすく手軽にデザイントークンを設定できる点とスペーシングの値が既に用意されている点です。
デザインシステムが存在しないデザインではよくスペーシングの値が揺れがちです。(15px
と16px
が混在している、16px
,20px
のように 4 刻みのスペーシングに対していきなり50px
のような突拍子のない値が使われているなど)
これに対して 1 からデザインシステムを実装者だけで作るのは大変です。デザインシステムをコード上だけで実装してもデザインの意図に対して変更しずらくなってしまうかもしれません。
しかし、Tailwind CSS が既に用意してくれているデザインシステムにのっとってトークンを使いながら実装しつつ、都度設定を書き加えていくことで、デザイントークンの恩恵を受けつつデザイナーの意図に合わせて柔軟に実装を対応させていくことができます。
また、Tailwind CSS で使えるクラス名はデザイントークンに紐付いているため、デザイントークンの使用が自然と強制されます (w-[50px]
などの表記で実装できるarbitrary values
を使うポイントは考えなければいけませんが・・・)
IDE 支援でスピーディーに実装できる
Tailwind CSS IntelliSense を使うことで、デザイントークンとして設定した値をリアルタイムに確認しながら実装することができます。
最近の開発ワークフローにおいて IDE 支援はかなり存在感が大きく、優秀な IDE 支援なしにワークフローについて検討するのはナンセンスでしょう。例えば、TypeScript によって JavaScript に型の恩恵をもたらせつつ高速に開発できるのは、IDE が TypeScript をサポートしていて適切な型やサジェストを行ってくれるからです。
他の CSS 設計では、IDE 支援の体験は「忘れられがち・あっても細かいところまでの結合は気にされない」という状況が多いですが、Tailwind CSS では最高の IDE 支援を最初から提供してくれています。
スタイルに対して実装方法を一貫させた Opinioned な Utility class を提供している
Tailwind CSS が Utility Class を使ったスタイルを実装していることによる CSS 設計への影響を 3 点ほど挙げていきます。
まず、自然とスタイルの選択が Tailwind CSS の Opinion によります。ようはスタイルの実装方法に対して実装・変更しやすいように Tailwind CSS が選んでくれています。
例えば Grid デザインを実装するときのことを考えてみましょう。
Grid を使って実装するときはgrid-template-areas
を使ったりgrid-template-columns
を使ったり様々な表現方法がありますが、Tailwind CSS が標準で提供するクラスにgrid-template-areas
に対応するものは存在しません。(拡張は簡単にできます。)
他にも、メディアクエリではmin-width
が優先して用意されていたりなど、スタイルの実装に対して Tailwind CSS はかなり意見を持っている(Opinioned)であることが分かります。
Tailwind CSS の用意されている Utility Class に沿って実装することで、自然と Tailwind CSS が厳選した CSS の表現方法に近づいていきます。
高凝縮にすべきである文章構造とスタイル定義をコロケーションして実装できる
Utility Class の恩恵は、クラス名を文章構造(HTML)に直書きするのを半ば強制できる点でも現れています。
最近では、Kent C. Dodds 氏が提唱するコロケーションという考え方が流行っていますが、それと同じく HTML と CSS はできるだけ近い位置に記述した方が保守性が高くなります。
なぜなら、スタイルの実装は文章構造に大きく依存することが多いからです。逆にHTML と CSS を分離させることで、CSS が意図しないファイルから参照される危険性があります。
ソフトウェア設計においては、保守性を高めるためにはモジュールの凝縮度を高く、結合度を低くすべきだという考えがありますが、それと同じことで Utility Class を使って文章構造にスタイルを直書きすることはコンポーネントの凝縮度を高めます。
クラス名で詳細度を統一して実装できる
Tailwind CSS の Utility Class の最後の恩恵は詳細度のコントールする観点を統一できる点です。
今まで、CSS の実装において詳細度のコントールはセレクターによって行われてきました。が、詳細度を正確に人間が把握するのはかなり高度なスキルです。
Tailwind CSS は、すべてクラスを介してスタイルを実装するので、詳細度のコントールできる観点が限られています。
今まで、様々な要因が詳細度に影響をあたえていましたが、Tailwind CSS はすべてクラス名とクラス名の並べ方に依存しています。
- メディアクエリのクラス(例:
w-4 md:w-8
) - クラスの並び順 (例:
pl-8 pl-4
ではpl-4
が優先される) -
!
を使った!important
の指示
これさえ気にしておけば大丈夫になります。
また、クラス名の並び方はprettier-plugin-tailwindcssなどのフォーマッター・リンターを使うことで人が気にすることなく勝手に並び替えられるように統一しておくと良いでしょう。
このクラスの並び方で詳細度が変わるという点を利用して、もし生の CSS を書きたいときは一意のクラス名を用意してスタイルを当てるという方法で詳細度を管理しやすく実装できます。
まとめ
Tailwind CSS を採用するという CSS 設計のポイントは、CSS 設計をプロジェクトに合わせて考えずとも Tailwind CSS が提供するワークフローに沿って開発するだけで、自然と保守性の高い CSS を実装できる点でした。
また、Tailwind CSS を選択するという CSS 設計は、CSS を実装する実装者がプロジェクトをまたいでも Tailwind CSS の知識を再利用して実装できるという点もあります。これは、Web 制作会社においてはとてつもなく強い利点になるでしょう。(もちろん、Web アプリケーション開発の現場でも!)
以上のようにCSS の実装が技術的負債になることを回避する視点では、Tailwind CSS はとても優れたフレームワークだと思います。
また、記事を書くにあたって、WEB+DB PRESS Vol.133 の特集『Tailwind CSS 実践入門——まず作ってから、あとで共通化する』を強く参考にさせていただきました。記事で紹介した Tailwind CSS が整理してくれるワークフローの利点についてより詳細に説明してくださっているので、この記事の内容に興味のある方はぜひこちらを参考にしてください。
-
『tailwind 絶対数年後に激ヤバ負債になってるという確信がある』(https://twitter.com/mizchi/status/1660949807199055873?s=20)というツイートを発端にした一連のツイート ↩︎
-
『内部品質について考えてみた』(https://zenn.dev/otkshol/articles/d73200272008e4)を参考にさせていただきました。 ↩︎
-
『SPA における CSS についてもうひとつの解』(https://yoshiko-pg.github.io/slides/20180419-scramble/#15)などの事例 ↩︎
Discussion