20251007

2025/10/07

Tailwind CSS

https://tailwindcss.com/docs/

コンセプト

Tailwind CSSでは1目的(single-purpse)のクラス(ユーティリティクラス)を複数組み合わせてスタイリングを行う。

<div class="mx-auto flex max-w-sm items-center gap-x-4 rounded-xl bg-white p-6 shadow-lg outline outline-black/5 dark:bg-slate-800 dark:shadow-none dark:-outline-offset-1 dark:outline-white/10">
  <img class="size-12 shrink-0" src="/img/logo.svg" alt="ChitChat Logo" />
  <div>
    <div class="text-xl font-medium text-black dark:text-white">ChitChat</div>
    <p class="text-gray-500 dark:text-gray-400">You have a new message!</p>
  </div>
</div>
  • クラス名を考えなくていい
  • スタイルの追加、削除が容易でスタイル間の影響を考えなくていい
  • 不要なクラスをビルド時に削除することで必要最小限のバンドルサイズにできる
  • 再利用が容易

というメリットがある。

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

  • マジックナンバーを制限できる
    • あらかじめ決めたサイズを選ぶだけにすることで制約が生まれる
  • ホバー、フォーカス時のスタイルの指定が可能(hover:、focus:
<button class="bg-sky-500 hover:bg-sky-700 …">Save changes</button>
  • レスポンシブ(画面サイズ)ごとのスタイルの指定が可能(sm:、md:
<div class="grid grid-cols-2 sm:grid-cols-3">
  <!-- … -->
</div>
  • ダークモード、ライトモードのスタイルの指定が可能(dark:***)
<div class="bg-white dark:bg-gray-800 rounded-lg px-6 py-8 …">
  …
</div>

CSSの合成

filterblurgrayscale を割り当てたい時、 blur-sm grayscale とクラスを指定すれば適切にCSS合成される。

どういう仕組みか気になったがCSS変数を使っているらしい。

.blur-sm {
  --tw-blur: blur(var(--blur-sm));
  filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);
}
.grayscale {
  --tw-grayscale: grayscale(100%);
  filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);
}

各クラスで変数定義と全てのfilterを設定することで実現している。

任意値(arbitrary values)の使用

[…]を使用することであらかじめ指定した値以外を使用することができる。

<button class="bg-[#316ff6] …">Sign in with Facebook</button>

これは「マジックナンバーを制限できる」のメリットと相対するもので推奨はされないが、柔軟性のある例。

これはCSSの仕組みだけでは無理なのでビルド時に細工が走って独自のクラスが作成される。

.bg-\[\#316ff6\] {
  background-color: #316ff6;
}

複雑なセレクタ

<button class="dark:lg:data-[current]:hover:bg-indigo-600">Button</button>

これもビルド時に細工が走る。

@media (prefers-color-scheme: dark) {
  @media (min-width: 1024px) {
    [data-current]:hover {
      background-color: #4f46e5; /* bg-indigo-600 */
    }
  }
}

見づらいが他に方法がないので弱点と言えば弱点。

任意バリアント

<div class="[&>[data-active]+span]:text-blue-600 ...">
  <span data-active><!-- ... --></span>
  <span>This text will be blue</span>
</div>
div > [data-active] + span {
  color: var(--color-blue-600);
}

だいぶ見づらい。ここまでやる場合はTailwindCSSだけでやるより別途CSSを書くなりしたほうが良さそう。

インラインCSSでの値の受け取り

Reactなどのライブラリと併用する際に動的に値を受け取りたい場合。

<button
  style={{
    "--bg-color": buttonColor,
    "--bg-color-hover": buttonColorHover,
    "--text-color": textColor,
  }}
  className="bg-[--bg-color] text-[--text-color] hover:bg-[--bg-color-hover] …"
>
  {children}
</button>

一旦CSS変数で受け取り、CSS変数名でTailwindCSSから参照する。

カスタムCSS

独自にCSSを書く方法もある。が、TailwindのテーマCSSを使って書く方法が推奨されている。

@layer components {
  .btn-primary {
    border-radius: calc(infinity * 1px);;
    background-color: var(--color-violet-500);
    padding-inline: var(--spacing(5));
    padding-block: var(--spacing(2));
    font-weight: var(--font-weight-semibold);
    color: var(--color-white);
    box-shadow: var(--shadow-md);
    &:hover {
      @media (hover: hover) {
        background-color: var(--color-violet-700);
      }
    }
  }
}

!important

クラス名の最後に!をつけると !important 指定となる。

これもビルド時の細工。

その他

指定できるものを全て書くとキリがないため、使いながら覚えていくのが良さそう。
https://tailwindcss.com/docs/

React + Tailwind CSSの始め方(Vite)

Tailwind CSS v4から設定方法が変わったため注意。

v4以降ではpostcss、autoprefixerが不要となった。

npm create vite@latest test_project -- --template react-ts
cd test_project
npm install tailwindcss @tailwindcss/vite

@tailwindcss/vite は TailwindCSS の Vite 用プラグイン。

Vite設定値の読み込みやCSSのビルドを行う。

// vite.config.js でプラグイン設定

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
})

index.cssの内容を消して、tailwindcssをインポート

@import "tailwindcss";

App.tsxの内容を以下に変更して反映されてれば成功。

function App() {
  return (
    <>
      <h1 className="text-3xl font-bold underline">
        Hello world!
      </h1>
    </>
  )
}

export default App

ちなみにブラウザ由来のCSSもリセットされた状態となるため別途reset.cssなど用意する必要はない。

React + Tailwind CSSの始め方(Next.js)

Next.js + Tailwind CSSのボイラープレートがある。

Next.js

  • サーバーコンポーネントはキャッシュされる?
    • というより別ページに遷移した時、Layoutなど変更が不要な箇所は再利用される?
      • chromeの開発者ツールだけではよく分からず。
    • 同じLayoutを使う場合は使いまわす、という処理があってもおかしくはない。
    • 「RSC ペイロード」「Flight プロトコル」などの要素が関連しそう
    • 検索用
      • Next.js Flight protocol explained
      • React Server Components payload internals
      • Next.js RSC payload structure layout reuse
      • Next.js router shared layout rendering
      • React Flight serialization format
      • Next.js app router partial rendering

microCMS + Tailwind CSS

microCMSで記述したリッチテキストはh1などのタグのみでクラスは指定されていない。(idは自動的に付与される)

そんな時は @tailwind/typography でスタイルを割り当てるのが良いらしい。

npm install -D @tailwindcss/typography
// globals.css
@import "tailwindcss";
@plugin "@tailwindcss/typography"; // 追加
<!-- 記事のラッパー要素に prose クラスをつける -->
<div className="prose" dangerouslySetInnerHTML={{ __html: blog.body }} />

prose は「散文」という意味。

ただ @tailwind/typography を使用する場合、TailwindCSSのクラスを使って独自のスタイルを指定することができない。

TailwindCSSのクラスを使って好みのスタイルを当てたい場合は返ってきたHTMLをcheerioなどでパース → 各タグごとにあらかじめ用意しておいたクラスを付与、という方法がいい?

色々やってみた結果、 @tailwind/typography もcheerioも使わずにglobals.cssの @layer components で各タグのクラスを指定するのがシンプルそう。

<!-- 記事のラッパー要素に blog-detail クラスをつける -->
<div className="blog-detail" dangerouslySetInnerHTML={{ __html: blog.body }} />
// globals.css
@layer components {
  .blog-detail {
    h2 {
      @apply text-2xl font-semibold mt-6 mb-4 text-gray-800;
    }

    h3 {
      @apply text-xl font-semibold mt-5 mb-3 text-gray-700;
    }
  }
}

ちなみにTailwind CSSでは base commponents utilities の3つのレイヤーを定義していてこの順番で反映される。

今回はそのcomponentsレイヤーにスタイルを追加している。