Tailwind CSS実践入門 ~まず作ってから、あとで共通化する

Tailwind CSS実践入門
第1章 ユーティリティファーストとは何か
─⁠─従来の課題を解決する大胆なアプローチ

CSSフレームワークのTailwind CSSが注目を集めています。その特徴は、flexpt-4text-centerといったユーティリティクラスの組み合わせだけで、ほとんどすべてのスタイリングをしてしまおうという大胆なアプローチです。これまでのベストプラクティスと真っ向から対立するようなやり方ですが、だからこそ享受できるいくつもの強みがあります。本特集では、そうしたTailwind CSSの考え方や、具体的な使い方について紹介します。

お知らせ本特集のサンプルコードは、GitHubの筆者リポジトリからダウンロードできます。https://github.com/yuheiy/wdpress133_tailwind

本特集では、Tailwind CSS(以降、Tailwind)の考え方や使い方を紹介します。第1章は基礎編で、第2章から第3章では具体的な使い方を紹介し、第4章ではデザインとCSSの関係について考察します。

Tailwind CSSとは ─⁠─ユーティリティクラスによる大胆なアプローチ

近年、CSSフレームワークのTailwind CSSが注目を集めています。Tailwindの特徴は、flexpt-4text-centerといったユーティリティクラスの組み合わせだけで、ほとんどすべてのスタイリングをしてしまおうという大胆なアプローチです。Tailwindを使うと、用意されたユーティリティクラスの組み合わせだけであらゆるデザインを実現できるようになります。そのため、自分でCSSを記述することがほぼなくなります。

Tailwindは、BootstrapやBulmaのような一般的なCSSフレームワークと違って、既成のスタイルやコンポーネントの提供を目的としたものではありません。それよりも、ユーティリティクラス中心のワークフローを実現するための開発環境を提供するものであり、CSS in JS[1]やCSS Modules[2]に近い立ち位置にあります。

たとえば、図1のようなデザインを実装するとします[3]

図1 チャットが届いたことを通知するUI
図1

CSSを書くための一般的なやり方にのっとれば、次のようなマークアップになるかもしれません。

<div class="chat-notification">
	<div class="chat-notification-logo-wrapper">
		<img
			class="chat-notification-logo"
			src="/img/logo.svg"
			alt="ChitChat Logo"
		>
	<div class="chat-notification-content">
		<h4 class="chat-notification-title">ChitChat</h4>
		<p class="chat-notification-message">You have a new message!</p>
	</div>
</div>

しかし、Tailwindを使って同じものを実装するとすれば、次のようになります。

<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4">
	<div class="shrink-0">
		<img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">
	</div>
	<div>
		<div class="text-xl font-medium text-black">ChitChat</div>
		<p class="text-slate-500">You have a new message!</p>
	</div>
</div>

付与されているクラスの数に驚かれるかもしれませんが、これらがTailwindのユーティリティクラスです。ユーティリティクラスには単一の機能が割り振られており、基本的には、1つのユーティリティクラスが1つのCSSプロパティと対応します。

これを見ると、⁠ちょっとあり得ない」と拒絶反応を抱くかもしれません。なぜなら、これまで良しとされていたことと真っ向から対立するようなやり方だからです。

ユーティリティクラスのメリット

それでも、否定したくなる気持ちを抑えて、実際にTailwindを使ってみると、いくつもの大きなメリットがあることがわかります。

クラス名を考える必要がなくなる

「CSSにおいて最も難しいのは名前を付けることだ」と言われることがあるほど、CSSの命名は苦労するものです。CSSでは命名の機会が非常に多く、かつ、そのための基準も捉えどころがない場合がよくあります。

Tailwindを使うことで、新たなクラス名を考えることなくスタイリングができます。それだけで、作業の負担は大きく軽減されます。

HTMLファイルとCSSファイルを行き来しながら作業する必要がなくなる

CSSを書く際には、多くの場合、HTMLファイルとCSSファイルを交互に行き来しながら作業する必要があります。CSSを書くためにはHTMLの構造を意識する必要があるので、HTMLから書き始めることが普通ですが、同時に、HTMLを書くためにもCSSの都合を考える必要があります。そのため、一方を書き進める過程で、もう一方を修正する必要がたびたび生じます。

作業のさなかでは、HTMLとCSSを行き来するとともに、ブラウザでの描画結果も確認する必要があり、開発者は注意を向ける対象を何度も頻繁に切り替えることになります。これによって余計に認知的な負荷がかかってしまい、ミスを誘発したり、作業時間を余計に費やしたりする結果になります。

Tailwindを使うと、HTML上だけでスタイリングの作業が完結します。これは思いのほか大きな違いです。

CSSの影響範囲が明確になる

どの要素にどのルールが適用されるのかがわかりづらい、というのもCSSの難しさの一端です。CSSのスタイルの多くは、セレクタを経由して間接的に適用されるため、HTMLを見るだけではどのような結果になるのかが把握できません。そのため、CSSの変更は慎重に行う必要があります。ある変更によって思いもしない箇所に影響が及び、知らないうちに壊れてしまうことが珍しくないからです。

Tailwindを使うと、そういった問題も解消されます。ユーティリティクラスが影響を及ぼす範囲は明白で、意図しない部分に作用することがありません。要素に対して、実質的に、スタイルそのものが直接適用されているからです。そのようにしてできたHTMLは、変更するのがとても簡単です。

コーディングガイドラインが不要になる

CSSは落とし穴の多い言語で、慎重に書き進めていかなければすぐにメンテナンスが困難な状態に陥ってしまいます。そこで、不用意に問題のある使い方をしてしまうことを防ぐために、コーディングガイドラインが設けられることがよくあります。

CSSのガイドラインとして有名なものを一つ挙げるとすれば、Harry Roberts氏によるCSS Guidelinesがあります。管理しやすく、スケールするCSSを作成する方法について、徹底的に論じているものです。それだけに分量としては極めて膨大で、ページを上から下までスクロールするだけで圧倒されてしまうほどです。

もっとも、実際の現場のガイドラインとして、これだけのトピックが盛り込まれることはまれです。しかし、CSSはかなり自由な書き方ができてしまう言語であるがゆえに、そこに秩序をもたらすためには相当な努力が要求されることは間違いありません。ガイドラインの多くは、現場ごとに独自に作成されます。プロジェクトの状況や、担当者の経験に応じて、それぞれ違った内容のものがいくつも乱立することになります。もちろん、それらの出来もまちまちです。すると、プロジェクトに参画するたびに別々の内容を理解する手間が生まれたり、ルールに無理が生じて守られなくなったりします。そうして形骸化してしまうことがほとんどです。

本来、理想的なのは、そのようなガイドラインがなくてもうまくいくようになっていることです。Tailwindを使うと、そもそも開発者がCSSを記述する機会がほとんどなくなるため、CSSの書き方で悩む必要はなくなります。Tailwindというフレームワークを介することで、問題のある書き方は最初からできないようになっているわけです。

CSSのファイルサイズが小さくなる

一般的なやり方では、新しいスタイルが必要になるたびにCSSを書き足していきます。特定のコンポーネント専用にCSSのルールセットを記述しているため、たとえ同じルールが複数の箇所で必要になったとしても、それぞれの用途ごとに重複した記述をせざるを得ません。そのため、スタイルの数が増えれば増えるほどファイルサイズも増大し、パフォーマンス上の問題につながります。

しかしTailwindの場合、1つのユーティリティクラスを複数の箇所で繰り返し使用できます。すると、1つのルールセットだけで複数の要素のスタイリングができるため、ルールセットが重複せず、ファイルサイズを増加させずに済ませられます。

環境を選ばずに採用できる

Tailwindと並んで、CSSを書くための新しいアプローチとしてはCSS in JSやCSS Modulesがありますが、いずれも使用できる環境が限られています。JavaScriptの実行環境が必要になるため、クライアントサイドであったり、Node.jsを使用したサーバ環境でしか動作させることができません。そのため、サーバサイドでNode.js以外の言語を使用してHTMLを生成する環境がある場合、そこに導入するのは困難です。

Tailwindの場合、あらかじめCSSファイルをビルドできるしくみさえあれば、どのような環境でも問題なく組み込めます。

インラインスタイルとの違い

Tailwindのアプローチを見ると、これはインラインスタイルと変わらないのではないかと思われるかもしれません。要素に対して直接的にスタイルを適用するという意味ではかなり似通っています。

一方、インラインスタイルでは実現できない機能も存在します。Tailwindのユーティリティクラスを使うことで、そうした機能も利用できます。

メディアクエリの使用

インラインスタイルでは、メディアクエリを使用できません。一方、Tailwindではメディアクエリのブレイクポイントに応じたユーティリティクラスが提供されています。これによって、レスポンシブデザインにも問題なく対応できます。

<img class="w-16 md:w-32 lg:w-48" src="...">

この例では、デフォルトでは幅が164remになり、中画面では328rem⁠、大画面では4812remになります。

擬似クラスや擬似要素の使用

インラインスタイルでは、ホバーやフォーカスなどの擬似クラス、::beforeなどの擬似要素を使用できません。Tailwindでは、それらに対応するユーティリティクラスが提供されています。

<button class="bg-sky-500 hover:bg-sky-700 ...">変更の保存</button>

デザイントークンに基づいた値の制約

インラインスタイルでは、CSSの書き方を制約することができません。そのため、たとえば色はあらかじめ定義されたカラーパレットから選択する、余白の大きさは4pxの倍数にする、といったルールを設けたとしても、容易にそこから逸脱してしまいます。そこでTailwindでは、ユーティリティクラスで使用できる値をあらかじめ定義できるようになっています。これによって、ルールに基づいた値だけを使ってスタイリングできるようになっています。

たとえば、次のように設定します。

tailwind.config.js
module.exports = {
	theme: {
		screens: {
			sm: '480px',
			md: '768px',
			lg: '976px',
			xl: '1440px',
		},
		colors: {
			'blue': '#1fb6ff',
			'purple': '#7e5bef',
			'pink': '#ff49db',
			'orange': '#ff7849',
			'green': '#13ce66',
			'yellow': '#ffc82c',
			'gray-dark': '#273444',
			'gray': '#8492a6',
			'gray-light': '#d3dce6',
		},
		fontFamily: {
			sans: ['Graphik', 'sans-serif'],
			serif: ['Merriweather', 'serif'],
		},
		extend: {
			spacing: {
				'128': '32rem',
				'144': '36rem',
			},
			borderRadius: {
				'4xl': '2rem',
			}
		}
	}
}

ただし、必ずしもこの定義から逸脱できないというわけでもありません。その場限りのスタイルに対応するために、任意の値に基づいたクラスを作成する機能も提供されています。

<div class="top-[117px]">
  <!-- ... -->
</div>

スタイルの再利用

ユーティリティクラスを使うと同じスタイルのセットを再利用できなくなるのではないか、と思われるかもしれません。というのも、ユーティリティクラスが使用されていると、スタイルの変更のたびにHTMLに手を入れる必要があります。そのため、もし同じスタイルになるべきHTMLが複数箇所に重複して記述されていれば、その都度すべての箇所に手を入れる必要があります。

しかし昨今の開発においては、ReactやVue.jsのようなコンポーネント構築のしくみや、BladeやTwig、ERBなどのテンプレート言語が採用されているのが普通です。そうした言語の機能を使うと、同じクラスの組み合わせを重複して記述せずに複製できます。

たとえば、何かしらの情報を一覧表示するUIがあれば、ループ機能を使ってテンプレートを実装するでしょう。

<ol class="divide-y">
  {#each posts as post}
    <li>
      <a
        class="flex items-center py-2 gap-x-4"
        href="{post.link}"
      >
        <span class="flex-1">{post.title}</span>
        <time class="text-sm text-gray-600">
          {post.date}
        </time>
      </a>
    </li>
  {/each}
</ol>

するとループの内側のHTMLは、ソースコード上では重複なく記述されていることになります。あるいは、複数の箇所でスタイルを再利用する必要があれば、HTMLのまとまりをコンポーネントやパーシャル[4]として切り出すことができます。

Card.vue
<template>
	<div>
		<img class="rounded" :src="img" :alt="imgAlt">
		<div class="mt-2">
			<div>
				<div class="text-xs text-slate-600 uppercase font-bold tracking-wider">
					{{ eyebrow }}
				</div>
				<div class="font-bold text-slate-700 leading-snug">
					<a :href="url" class="hover:underline">{{ title }}</a>
				</div>
				<div class="mt-2 text-sm text-slate-600">{{ pricing }}</div>
			</div>
		</div>
	</div>
</template>

このようにすることで、同じスタイルを繰り返し使用できます。もし変更が必要になっても、一ヵ所のソースコードを変更するだけで済みます。

もっとも、出力されるHTMLを見れば、同じユーティリティクラスの羅列が繰り返されることにはなります。しかし、あくまでソースコード上での重複がなければ問題ないというわけです。必ずしもCSSだけで再利用性の問題を解決する必要はないのです。

ユーティリティファーストが実現すること ─⁠─早すぎる抽象化を避ける

結局テンプレートで共通化するのであれば、最初からユーティリティクラスに頼らずにCSSを書いておけばよいのではないか、と思われるかもしれません。しかし従来のやり方の問題は、すべてが共通化されたもの、つまりコンポーネントになってしまうことです。

CSSにおいては、共通化するに値するものとそうでないものの両方があります。共通化すべきものを共通化しないでいたり、逆に、共通化すべきでないものを共通化していたりすると、設計上の歪みをもたらします。すべてを共通化すべき、あるいはすべてを局所化すべき、という二元論ではなく、両者が等しく必要ということです。Tailwindのユーティリティクラスとテンプレート言語を組み合わせることで、はじめてその両立が可能になるのです。

Tailwindが掲げているのは、⁠ユーティリティファースト」というコンセプトです。これが意味するのは、最初はユーティリティクラスで作り、そして、本当に再利用する必要が生じてからパーツとして抽出すべし、ということです。

さらに言えば、Tailwindの強みは、あとからの抽出をスムーズに行えることです。ユーティリティクラスによってスタイリングされたHTMLは、その中の対象となる箇所をコピー&ペーストするだけでパーツとして抽出できます。従来のやり方ではこうはいきません。従来のやり方においては、一度コンポーネントとして実装したものをあとから変更する場合、それなりの手間がかかってしまいます。まず、すべてのクラスがコンポーネント専用のものになっているため、そのまま流用することはできません。そのため、抽出するためには、対象箇所に含まれるクラス名を変更し、新しいコンポーネントを作成し、そして新しいコンポーネントに応じたCSSを実装しなおすという煩雑な作業が発生します。

しかし考えてみれば、テンプレート言語の機能によってコンポーネント化されることを前提としたとき、クラス名までもがコンポーネント名と対応しているのは冗長です。テンプレート言語の機能によってコンポーネント化されているのにもかかわらず、その中で使われるクラス名までそうなっていると、コンポーネントのための体系が二重に存在することになります。次の例では、Vue.jsの機能によってCardという名前の付いたコンポーネントになっていることに加えて、使われているクラス名にもcardという接頭辞が付与されています。

Card.vue
<template>
	<div class="card">
		<img class="card-image" :src="img" :alt="imgAlt">
		<div class="card-content">
			<div class="card-content-inner">
				<div class="card-eyebrow">{{ eyebrow }}</div>
				<div class="card-title">
					<a :href="url" class="card-title-link">{{ title }}</a>
				</div>
				<div class="card-pricing">{{ pricing }}</div>
			</div>
		</div>
	</div>
</template>

つまり、これは過度な抽象化です。Tailwindを採用することでそうした問題も解消されます。

従来のやり方の問題

しかし、ここまでユーティリティクラスの長所を見てきても、やはりどうしても従来のやり方を断ち切れないこともあるでしょう。では、そもそもそのようなやり方をしているのはなぜでしょうか。あらためて振り返ってみます。

HTML仕様書では、コンテンツのプレゼンテーションではなく、コンテンツの性質に基づいたクラス名を使用することが推奨されています。プレゼンテーションとは、どのような体裁で表示されるかということです。

コンテンツの性質に基づいたクラス名を使用すると、HTMLはそのままにCSSを変更するだけで、すべてを再構築できることになります。この理論を最大限に活かしたアプローチが、CSS Zen Gardenです。CSS Zen Gardenは、1つのXHTML文書に対して、さまざまな異なるCSSを適用することで、いかに多様なデザインが実現できるかを紹介しているサイトです。

しかし現実的には、同じHTMLのまま表現できるプレゼンテーションには限界があります。ある視覚的なレイアウトを実現するには、HTMLの構造もそれと対応したものにならざるを得ないからです。そのため、あとから別のスタイルに変更する際には、CSSを変更するだけでは不十分で、HTMLも一緒に変更しなければならないことが少なくないのです。スタイルが複雑になればなるほど、HTMLは特殊な構造を取るため、その傾向は強まっていきます。つまり、これはある程度素朴なデザインでしか機能しないコンセプトなのかもしれません。

それにもかかわらず、あくまでクラス名はプレゼンテーションから分離されているべき、という慣習は残っています。あるスタイルを実現するためだけでしかない構造を、頭を捻って無理やり抽象化するという、いかにも無理のある不自然な実装方法が強いられているのです。

Tailwindのユーティリティクラスは、そうした抽象化なしの直接的なスタイリングを可能にします。要素に特別な名前を付けることなく、そこにあるがままでしかない状態でスタイリングができるようになるのです。下手に抽象化されたクラス名を考え出すよりも、ほとんど抽象化されていない愚直なクラス名を使う方が優れていますし、より実直です。

一般に、プログラミングでは名前を付けることが重要と言われますが、何にでも名前が付いていればよいというわけではありません。そもそも命名において重要なのは、意味のある何かに対して名前を付けることです。たとえば、何かしらの処理の集まりに重要性を見い出したとき、それを取り立てて扱うために、本質的な意味に基づいた名前を付け、概念化するのです。名前を付けるのは、そこに特別な意味があるからこそです。いたずらに名前を付けて抽象化しても逆にわかりづらくなってしまいます。

ただし無視できないのは、ユーザーもまたセレクタを使い得るということです。ユーザーとは、Webページの一般の利用者のことを指しています。

一部のブラウザでは、Webページに対して、ユーザー独自のスタイルを定義できる機能を備えています。この機能を使うと、ユーザーの希望に応じてスタイルを上書きできます。たとえば、Webページの文字の行間が狭くて読みづらいときに広げたり、不要な情報が表示されないように削除したり、といった制御ができます。

ユーザー独自のスタイルを定義する際には、普通にCSSを書くのと同様に、セレクタを指定して宣言を記述することになります。その際、従来のやり方で実装されたHTMLであれば、ユーザーは容易に対象の要素を選択できます。一方、Tailwindを使う場合、対象の要素を識別するためにクラスが使えなくなり、要素の選択が難しくなります。

理想は、ユーザーにとってできる限り多様な方法で利用できることです。場合によっては、そうしたユーザーのために、何かしらのフックになるような実装をすることを検討してもよいでしょう。

Tailwindの問題 ─⁠─HTMLの可読性の低下

Tailwind を使用することのデメリットとしては、HTMLが非常に読みづらくなってしまうことです。もっともソースコードについては、テンプレート言語の機能によって目的に応じた構造化ができれば、ある程度読みやすくすることはできます。さらに整形ツールを使用したり、エディタの設定などを調整することで、問題は多少軽減されます。

しかし、最終的にブラウザに出力されるHTMLは、なかなかに解読しづらいものになります。ソースコードが構造化されていても、ビルド後にはすべてが一緒くたになってしまうためです。ブラウザの開発者ツールによる検証は少し面倒になります。

まとめ

従来のやり方と比べて、ユーティリティクラス中心のスタイリングにはさまざまなメリットがあります。加えて、Tailwindを採用するからといって、ユーティリティクラスだけですべてを構築するわけではない、という点も重要です。

次章では、実際にTailwindを導入するための方法について解説していきます。

おすすめ記事

記事・ニュース一覧