Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般
  • Ruby / Rails関連

Tailwind CSSをカオスにしないための5つのベストプラクティス(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

章立てや強調の一部は原文から変更しています。

参考: Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.

tailwindlabs/tailwindcss - GitHub

Tailwind CSSをカオスにしないための5つのベストプラクティス(翻訳)

はじめに

Tailwind CSSを使うと、作業がとてもスムーズかつ楽になります(だからこそ広く知られているのです)。HTMLにさまざまなTailwindクラスのリストを貼り付けるだけで、インターフェイスがたちまち魅力を放つようになります!

しかし、アプリケーションが成長すれば、クラスのリストも増えていきます。そしていつの日か、自分の書いたコードが理解できなくなり、アプリケーションの構造やマジック変数で混乱が始まり、作業が困難になってしまうことに気づくでしょう。

本記事では、このようなシナリオを避けるため、Tailwind CSSを賢く使いこなすためのベストプラクティスを皆さんに紹介いたします。

Tailwindを正確に賢く使いこなせば、頭痛の種も減り(ほとんどの)問題も解決できるようになります。しかし、そのためにはプロジェクトが2つの要件をクリアする必要があります。これらの要件を守らないと、Tailwindによって業務の遂行がむしろ困難になる可能性があります。

🔗 要件1: プロジェクトでデザインシステムを確立すること

Tailwindの哲学は、Tailwindと並列するデザインシステム(design system)1を支援し、強化することにあります。デザインシステムには、デザイナーや開発者が利用する統一されたデザイントークンも含まれます。

デザイントークン(design token)とは、「色」「スペーシング」「文字サイズ」などの分割できないアトミックな値であり、デザインの特徴を定義してプロジェクト全体で繰り返し再利用します。

たとえば、プロジェクトで色を揃えなければならない標準のボタンやタブがあるとします。

.button {
  background-color: oklch(45% 0.2 270);
}

.tab {
  background-color: oklch(45% 0.2 270);
}

このカラースキームをプロジェクトでほんの少し変更することが決まったら、さあ大変。すべてのビューでこのマジック変数のような色指定を調べて回り、それらをもれなく更新しなければならなくなります。この作業は一貫性が損なわれやすく、メンテナンスも困難になる可能性があります。


デザイントークンは、このような問題を未然に防ぎ、UI要素全体の統一を図るのに有用です。


ありがたいことに、Tailwindにおけるデザイントークンの実装はtailwind.config.jsでトークンを定義するだけでできます。

module.exports = {
  theme: {
    colors: {
      primary: 'oklch(45% 0.2 270)'
    }
  }
}

この新しい標準色にprimaryという名前を与えると、背景色ではbg-primary、テキスト色ではtext-primaryという具合に、アプリケーション全体で統一的な命名が使えるようになります。

<button class="bg-primary">Standard button</button>
<div class="bg-primary">First tab</div>

こうしておけば、プロジェクトでカラースキームを変更したくなったときでも、tailwind.config.jsただ1箇所の色を置き換えるだけで済みます。

プロジェクトでデザインシステムの導入を検討すらしていないのであれば、Tailwind CSSの利用は避ける方が身のためです。さもないと、'p-[123px] mb-[11px] gap-[3px]'のようなマジック変数まみれのCSSクラスリストを山ほど書きちらしたり、スペーシング設定をいじって15px16px17pxのような場当たり的なトークンをむやみに増やしたりするはめになり、最終的にカオスに満ちたコードの一丁上がりとなります。


統一されたデザインシステムの導入は、デザイナーチームと開発チームの相互理解を深めるのに有用です。


たとえばFigmaを使うことで、デザインシステムの信頼のおける情報源を一箇所に集約して共有できるようになります。ただし、デザインシステムを真の意味でメンテナンス可能にするには、トークンのグループ化や命名に関する規約も定めておく必要があります(後述)。

参考: Figma: コラボレーションインターフェースデザインツール

🔗 要件2: プロジェクトにコンポーネントベースの手法が既に導入されていること

Tailwind CSSの「ユーティリティファースト」の手法ではHTML要素に直接Tailwindクラスを適用するので、油断するとHTML構造があっという間に散らかって冗長になり、マークアップを読むのもメンテするのもつらくなってしまう可能性があります。この問題は、特にプロジェクトが成長するに連れて顕在化します。


解決方法は、頻繁に再利用されるパターン(ここでは繰り返し出現するHTML要素)を積極的にコンポーネントに分離してカプセル化することです。


コンポーネントによるカプセル化を用いることで、マークアップやコードをDRYにできます。さらに、信頼できるTailwindスタイル情報がそのコンポーネント内の1箇所に凝縮されるので、ここを変更するだけで簡単に更新できるようになります。

<!-- Tailwindクラスが多数定義されている再利用可能なボタン: -->
<button class="bg-yellow-700 border-2 font-semibold border border-gray-300 text-green p-4 rounded">
Custom Button
</button>

<!-- 上の構造を何度も書くのではなく、再利用可能なコンポーネントを作成する: -->
<CustomButton>Custom Button</CustomButton>

ただし、使っている開発ツールでコードをコンポーネントに切り出せない場合は、Tailwindのユーティリティファースト手法を導入しても開発がつらくなるだけでしょう。そうであれば、CSS Modulesなどの別のCSSフレームワークに目を向けるべきでしょう。

最後に、コンポーネントベースの手法で1つ大事な注意を述べておきたいと思います。

@applyディレクティブの利用は避けること。

.block {
  @apply bg-red-500 text-white p-4 rounded-lg active:bg-blue-700 active:text-yellow-300 hover:bg-blue-500 hover:text-yellow-300;
}

@applyディレクティブを使えば、たしかに上のようにコードは一見クリーンになるかもしれませんが、Tailwindの重要なメリットである「CSSクラス名を読むときの認知の負荷を減らす」機能をみすみす捨ててしまうことになります。しかも、@applyディレクティブはコンポーネントごとに隔離されずグローバルに効くので、「スタイルを変更しても他に影響しない」というメリットも失われてしまいます。さらに言うと、@applyディレクティブを使うとCSSバンドルサイズが肥大化します。

Tailwind CSSの作者たちも、「@applyディレクティブの利用には注意すべき」という重要な指摘をドキュメントで強調しています。

以上の要件を2つとも満たしていれば、Tailwind CSSはきっとフレームワークの素晴らしい選択肢のひとつとなるでしょう。ここからは、長期的な開発体験を快適にするための有用なベストプラクティスを5つご紹介します。

🔗 1. ユーティリティクラスは可能な限り減らすこと

HTML要素にTailwind CSSのユーティリティクラスを大量に指定してビルドすると、CSSクラスが増えるたびに複雑さが増して開発者が読みづらくなり、後になってコードを泣く泣く読み解きながら作業するはめになります(これを読んでるあなたもですよ)。CSSクラスのリストはTailwind CSSの本質的な機能であることは言うまでもないのですが、それでもユーティリティクラスはできる限り減らしておく方が後々身の助けになります。

Tailwind CSSのユーティリティクラスを減らしつつ同じ結果を得る方法をいくつか紹介します。

pt-4 pb-4

py-4
py-4と書いても、pxmxmyプロパティが追加されます。
flex flex-row justify-between

flex justify-between
flex-rowはCSSのflex-directionプロパティのデフォルト値です。この他のCSSプロパティについても(flex-wrapなど)、デフォルト値を覚えておくと同じスタイルを楽に書くときに役に立つことがよくあります。
border border-dotted border-2 border-black border-opacity-50

border-dotted border-2 border-black/50
border-2を指定するとborderは設定済みになります。border-black/50はRGBAフォーマットのショートハンドです。

CSSクラスのリストを減らしておけば、今後アプリケーションの構造をずっと楽に読み解けるようになります。

🔗 2. デザイントークンをグループ化し、意味がわかるように命名すること

チーム開発では、クリーンコーディングの実践(変数名は意味が明確になるように命名するなど)が長期的な開発できわめて重要であることに反対する人はたぶんいないでしょう。チーム開発に限らず、たとえ個人開発であろうと、コードを明確にするルールの制定は大きな価値があります。さもないと、自分のプロジェクトで自分が混乱するはめになるかもしれません(休憩から戻ったときに何をしていたのか忘れてしまうなど)。

特にTailwind CSSを利用する場合は、クリーンコーディングがひときわ重要になります。大量のTailwind CSSとデザイントークンを行き当たりばったりで無節操に使っていると、そのうちコードが混乱の極みに達する可能性があります。

上述したように、デザイントークンは優秀な手法であることは確かなのですが、その場の思いつきでデザイントークンをぺたぺた貼り付けることを繰り返していたら、tailwind.config.jsは目も当てられないほどのカオスになるでしょう。

この問題を解決するには、tailwind.config.js内のトークンを丁寧にグループ化しましょう。つまり、ブレークポイントや色などを表すデザイントークンを特定の領域に閉じ込めて、互いに干渉しないようにするのです。

module.exports = {
  theme: {
    colors: {
      primary: 'oklch(75% 0.18 154)',
      secondary: 'oklch(40% 0.23 283)',
      error: 'oklch(54% 0.22 29)'
    },
    spacing: {
      'sm': '4px',
      'md': '8px',
      'lg': '12px'
    },
    screens: {
      'sm': '640px',
      'md': '768px'
    },
  },
  //...
}

もうひとつ重要な点は、トークンの意味を表す命名規則がばらつかないように保つことです。そうすることで必要なトークンを見つけやすくなり、アプリケーションが成長してもそれに合わせて拡張しやすくなります。


たとえば、エラーを表す色を追加するのであれば、単にFigmaからbright-redトークンをtailwind.config.jsにコピペして済ませてはいけません。bright-redトークンはcolorsセクションに置き、errorのようなより適切な名前を与えましょう。こうすることで、システムの一貫性が大きく向上します。

🔗 3. CSSクラスの並び順を常に揃えておくこと

クリーンコーディング規約をもうひとつ紹介します。
Tailwind CSSのクラスを書く順序を決めてそれを維持することで、読みやすく理解しやすいものになります。

説明のため、CSSクラスの順序がばらばらになっているHTML要素をいくつか見てみましょう。

<div class="p-2 w-1/2 flex bg-black h-2 font-bold">
  ブロック1: クラスがソートされていない
</div>

<div class="italic font-mono bg-white p-4 h-2 w-3 flex">
  ブロック2: クラスがソートされていない
</div>

上のコードブロックのCSSクラスには、「ボックス」「モデル」「ディスプレイ」「フォント」などさまざまなカテゴリのクラスが含まれていますが、表示上の並び順がまるで定まっていません。

この無秩序な並びのCSSクラスは、以下のようにカテゴリごとに並べ替えることで修正できます。

<div class="flex h-2 w-1/2 bg-black p-2 font-bold">
  ブロック1: クラスはソート済み
</div>

<div class="flex h-2 w-3 bg-white p-4 font-mono italic">
  ブロック2: クラスはソート済み
</div>

CSSクラスの並び順を手動でメンテしようとすると時間も注意力もごっそり奪われてしまうので、公式のPrettier plugin for Tailwind CSSを使うのがおすすめです2

tailwindlabs/prettier-plugin-tailwindcss - GitHub

使い方やCSSクラスの並び順に関して詳しくは、以下のTailwind CSS公式ブログを読んでおくことをおすすめします。

参考: Automatic Class Sorting with Prettier - Tailwind CSS

🔗 4. ビルドサイズを最小限に抑えること

バンドルサイズは可能な限り小さくしておくことが重要です。ビルドが重くなればページの読み込みが遅くなってパフォーマンスも悪化し、ユーザーの不満もつのります。

Tailwind CSSでは数千個にのぼるユーティリティクラスが提供されていますが、1件のプロジェクトでそれらを1つ残らず使うことは普通はないでしょう。使っていないスタイルをproductionビルドに残さないようにするには、どうすればよいでしょうか?

Tailwind CSS 3.0以上を使っていれば、JIT(Just-in-Time)エンジンがデフォルトで有効になります。

参考: Migrating to the JIT engine -- Upgrade Guide - Tailwind CSS

JITエンジンが有効になっていれば、必要なスタイルだけが生成されるようになり、使っていないスタイルをproductionビルドから削ぎ落とす必要もなくなります。

ただし、3.0より前のTailwind CSSを使っている場合は、何らかの形でビルドに最適化を追加する必要があります。これは、未使用のCSSを削除するPurgeCSSツールを使えば可能です。Tailwind CSS v2の公式ドキュメントには、Tailwind CSS 2.1以前で設定する方法が記載されています。また、tailwind.config.jsで以下のように設定することでJITモードを手動で有効にすることも可能です。

module.exports = {
    mode: 'jit',
    ...
}

これで、必要なスタイルだけがバンドルに含まれるようになります。

もうひとつ注意点があります。
production向けビルドのCSSは常に最小化(minify)しておくこと

CSSを最小化すると、ホワイトスペースやコメントなどの不要な文字が削除され、ビルドのサイズが大幅に削減されます。

Tailwind CLIを使う場合は、以下のように--minifyフラグを指定すれば最小化されます。

npx tailwindcss -o build.css --minify

Tailwind CSSをPostCSSプラグインとしてインストールしている場合は、プラグインリストにcssnanoツールを追加することで最小化できるようになります。

There's a notable size difference after minifying CSS and enabling JIT mode, in our case, 8Kb compared to 12Kb

CSSは、最適化しないとサイズが非常に大きくなる可能性があります(数十キロバイト以上)。いくつかのコンポーネントにスタイルを追加しただけの小規模なプロジェクトであっても、JITモードを有効にしてCSSを最小化すればサイズが30%以上縮小されます。

Tailwind CSSで最小化を有効にするには、上述のようにminifyフラグを追加してjitモードを有効にするだけでできます。

Tailwind CSSの最小化や圧縮について詳しく知りたい場合は、以下の公式ドキュメントをどうぞ。

参考: Optimizing for Production - Tailwind CSS

ヒント

プロジェクトにデザイントークンを導入している場合は、デザイントークンのスタイルが実際に使われているかどうかを確認しておきましょう。

使いもしないデザイントークンが残っていると、開発者が混乱するばかりでなく、設定ファイルもデザインシステムも無駄に複雑になってしまいます。

🔗 5. 既存のスタイルをオーバーライドするときは一貫性を壊さないこと

以下のようなカスタムボタンを含むコンポーネントがページ上にあるとします。

<Button className="bg-black" />

そしてこのButtonコンポーネントにはデフォルトのスタイルが設定されているとします。

export const Button = () => {
  return <button className="bg-white">Test button</button>
}

この場合、ボタンの色はデフォルトの白のままになります。Tailwind CSSはスタイルを自動でオーバーライドして色を黒に変更したりしないので、色の変更は以下のようにButtonコンポーネントで行う必要があります。

export const Button = ({ className = "bg-white" }) => {
  return <button className={className}>Test button</button>
}

Tailwind CSSのこの側面自体は、本質的に何も問題はありません。しかし、大量のスタイルをオーバーライドまたは拡張する形で外観の一部を変更したい場合、その都度プロパティ経由でクラスを渡すと面倒が増える可能性があります。

さらに、オーバーライドや拡張によるスタイル変更には、別の欠点もあります。Tailwind CSSのユーティリティクラスをプロパティ経由で渡すと、コンポーネント表示の一貫性を保つのが難しくなる可能性があるのです。

この方法では、Tailwind CSSのユーティリティクラスの組み合わせをアプリ全体で同じコンポーネントに対して一律で指定することになるので、コンポーネントがアプリのどこでどう使われているかというコンテキストを考慮せずにスタイルが適用されてしまい、スタイルの一貫性が崩れてしまう可能性があります。

だとすると、他によい方法はあるでしょうか?

Tailwind CSSの任意のユーティリティクラスをプロパティ経由で指定する代わりに、以下のようにバリアントのセットを事前定義するのです。

const BUTTON_VARIANTS = {
  primary: "bg-blue-500 hover:bg-blue-600 text-white",
  secondary: "bg-gray-500 hover:bg-gray-600 text-white",
  danger: "bg-red-500 hover:bg-red-600 text-white"
};

そうしておいてから、Buttonコンポーネントにvariantプロパティを渡せるようにします。clsxというライブラリを使うと、classNameのコンストラクタを渡せるようになるので便利です。

export const Button = ({ className, variant = BUTTON_VARIANTS.primary }) => {
  return <button className={clsx(className, variant)}>Test Button</button>
}

lukeed/clsx - GitHub

ヒント

clsxは、クラスのコンストラクタを条件付きにしたい場合にも非常に便利です。

コンポーネントでclassNameのコンストラクタを準備できたら、後は使うだけです。以下のようにvariantを指定します。

<Button variant="secondary" />

これで一貫性も担保され、自由なカスタマイズに制約を加えたにもかかわらず柔軟性は失われていません。コンポーネントに新しいバリアントを追加するのも、既存のバリアントを編集するのも自由です。

この方法のもうひとつのメリットは、メンテナンスしやすいことです。Tailwind CSSのユーティリティクラスを1箇所で変更すれば、アプリ内のすべてのコンポーネントでバリアントの変更が反映されます。

何らかの理由で、事前定義済みのバリアントセットを使いたくない場合は、tailwind-mergeパッケージを試してみるとよいでしょう。

dcastil/tailwind-merge - GitHub

tailwind-mergeは、Tailwind CSSのクラスをJavaScriptで衝突なしにマージするtwMergeユーティリティ関数を提供しています。ただし、この関数は軽量とは言えず、バンドルサイズも肥大化するので、本当に必要な場合にだけ使うようにしましょう。

🔗 まとめ: Tailwind CSSの使いこなし方と「Tailwind CSSを使わないようにする方法」

Tailwind CSSは強力なツールですが、プロジェクトでカオスの発生を防ぐには、いくつかのルールを守って使うことが重要です。本記事で説明した原則を以下にまとめてみました。

まず何よりも、本記事のベストプラクティスを最大限に活用するには、プロジェクトが以下の3つをクリアしている場合にのみTailwind CSSを使うことが重要です。

  • プロジェクトにデザインシステムが導入済みであること
  • プロジェクトに一貫したデザイントークンが導入済みであること
  • プロジェクトでコンポーネントベースの手法を選択していること

再利用可能な要素をコンポーネントに切り出して分割しておかないと、遅かれ早かれTailwindのつらみが増してしまい、冗長で繰り返しだらけのHTML構造ができあがるだけです。

  1. CSSユーティリティクラスの個数は可能な限り減らしておくこと
  2. チーム内でコーディング規則を定めること
    デザイントークンをグループ化する、意味のある命名にするなど
  3. 同様に、一貫したCSSクラスの並び順を実装し、linterでクリーンなコードを担保すること
  4. バンドルサイズは最小限に抑えること
    不要なスタイルを含めないようにし、productionビルドの最終CSSは常に最小化する
  5. 必要であれば、コンポーネントで使うバリアントセットを事前定義する
    コンテキストに合わないスタイルや、スタイルのオーバーライドに関する問題を回避するのに有用

以上のルールを守ることで、Tailwind CSSを快適に長期間利用できるようになり、Tailwind CSSのあらゆるメリットをチーム全体で享受できるようになります。


Evil Martiansは、成長段階のスタートアップ企業をユニコーン企業に飛躍させるためにサポートいたします。開発ツールの構築やオープンソース製品の開発も行っています。ワープの準備が整ったお客様、ぜひフォームまでご相談をお寄せください!

関連記事

tailwindcss-rails README(翻訳)

実践ViewComponent(1): 現代的なRailsフロントエンド構築の心得(翻訳)

実践ViewComponent(2): コンポーネントを徹底的に強化する(翻訳)


  1. 訳注: Design system - Wikipediaデジタル庁が公開しているデザインシステムもどうぞ) 
  2. 訳注: VSCodeでは、HeadwindというPrettierなしで使えるTailwind CSSクラスのソート用プラグインも利用できます。ただしサードパーティ製品につき、ソート順は公式とは異なります。 

CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。