💅

styled-components から Tailwind CSS への移行作業を終えて

に公開

はじめに

以下の記事、または他の方々も発信されているように、先月3月にstyled-componentsがメンテナンスモードとなる発表がありました。

https://opencollective.com/styled-components/updates/thank-you

As I write this in 2025, styled-components as a project is in "maintenance mode". There are a number of reasons for this:

個人的に、React を学び始めた時からお世話になっていて、Reactのコンポーネント指向をCSS in JSというものを通じて感覚的に理解するのにすごく助けてもらいました。

実務案件をはじめ個人開発でもすべてstyled-componentsで実装していたので、この情報を受けてショックとともに「CSSどうしよ」と🙄。

先の記事ではCSSエコシステムにおいても言及されていて、筆者的にはTailwindという一つの指針を示してくれているように感じました。

Tailwind CSSに関しては、「styled-componentsがあるし、別にTailwindはいいや」と思っていた筆者ですが、今回の件で心境が変化した次第です。

そこで今回、10〜20ほどある実務・個人開発のほぼほぼ全てをstyled-componentsからTailwind CSSに移行したので、感想及び備忘録的な内容で記事にしていきたいと思います。

移行作業はリファクタリング含めて、大体2週間くらいで済みました。

対象読者

  • styled-componentsのメンテナンスモードにショックを受けている方
  • html, css に関してある程度の知識や経験を持っている方
    ※htmlタグの概要、CSSのセレクタやプロパティとか知っているレベル
  • スタイリングでよく使うような部分に絞ったTailwind CSSの情報が欲しい方
    ※筆者の独断と偏見に依存
  • React のCSSについて悩んでいる方
    ※ここに関しては記事の投稿者が述べていますがCSS in JSstyled-componentsEmotion)は選択肢から外したほうが良さそうです。詳しくはまた後述します。

For new projects, I would not recommend adopting styled-components or most other css-in-js solutions.

移行作業って大変?

結論から言うとCSSの知識が一定以上あればそこまで苦では無いと思います。
ここでいう一定以上とは、@keyframesを使った簡易なアニメーション処理をはじめ、バニラなCSSで特に不自由なく汎用的・一般的なスタイリングができるくらいのイメージです。

あと、最近は生成AIもありますからね。
筆者は今回Claude(無料プラン)を使って進めましたが、いくつか重複した記述になっていたり、冗長な書き方になっていたりする部分もあって完全に手放しというわけにはいきませんでした。
まぁ、もともとのCSSが悪かった可能性も……

とはいえ、ClaudeのおかげでTailwind CSSの記法について勉強になる部分も多かったですし、キャッチアップ効率という観点でとても良かったと感じています。

次に、今回の移行作業を通じて得た「筆者の独断と偏見によるTailwind CSSで頻繁に使った部分」を紹介していきます。

Tailwind CSSで頻繁に使った部分

まずは初め方です。
筆者は全てviteだったので以下を参照しました。

https://tailwindcss.com/docs/installation/using-vite

Framework Guides に Next, Nuxt などあったので網羅的に対応できそうなドキュメントです。

vite でのセットアップ詳細
  1. 当該プロジェクトへTailwind CSSをインストール
npm install tailwindcss @tailwindcss/vite
  1. vite.config.tsに設定を追加
import { defineConfig } from 'vite'
+ import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
  plugins: [
+    tailwindcss(),
  ],
})
  1. 使用するCSSファイル(src/index.css)にTailwind CSSをインポート
@import "tailwindcss";

リセットCSSについて

TailwindにはPreflightというデフォルトの CSS リセットが組み込まれているので追加でリセット CSS を用意する必要はないようです。

デフォルト: 1単位 0.25rem(4px)

Tailwind CSSではデフォルトで1単位が 0.25rem(4px)に相当(=1rem は 16px)します。

その他の補足情報

  • em値を再現したい場合は、カスタム値mt-[5em]を使用するのがベター

  • カスタム値[]について
    角括弧[]内に任意の値を指定することでTailwind CSSのユーティリティクラスで対応していない値を使用できます。(例:w-[32%], text-[#f5a623], mt-[1rem]

  • input type="file"のようなフォーム要素に関するもの(::file
    file:プレフィックスを付与してスタイリングします。

<input
  type="file"
  class="file:mr-4 file:rounded-full file:border-0 file:bg-violet-50 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-violet-700 hover:file:bg-violet-100 dark:file:bg-violet-600 dark:file:text-violet-100 dark:hover:file:bg-violet-500 ..."
/>

Tailwindの標準クラス(一部)

Tailwind CSSの標準クラス(ユーティリティクラス)は公式ドキュメントに詳しく載っていますが、大まかに分けると以下のようなカテゴリがあります。

レイアウト関連
  • display
    block / inline-block / flex / grid / hidden
  • width
    w-(幅: w-1/2, w-full など)
    full=100%
  • height
    h-(高さ: h-16, h-screen など)
    screen=viewport(h-screenの場合は100vh
  • min-width/height, max-width/height(最小・最大サイズ)
    min-w-, min-h-, max-w-, max-h-
タイポグラフィ
  • text-(テキストサイズ: text-sm=14px, text-xl=20px など)
  • font-(フォントウェイト/ファミリー: font-bold, font-serif など)
  • leading-(行間=line-height: leading-tight, leading-relaxed
  • tracking-(文字間隔=letter-spacing: tracking-wide, tracking-tighter
  • text-(色: text-red-500, text-gray-700 など)
    • no-underlinetext-decoration: none
スペーシング
  • m-margin: m-4, mt-2, mx-auto など)
  • p-padding: p-4, py-2, px-6 など)
ボーダー
  • border-(ボーダー: border, border-gray-300, border-t, border-b-[4px] など)
  • rounded-(角丸=border-radius: rounded-lg, rounded-full など)
Flexbox / Grid
背景(background
<!-- bg-blue-500 → background-color: #3b82f6; -->
<div class="bg-blue-500 text-white p-4">背景色:青</div>
<!-- bg-gradient-to-r → 右方向のグラデーション -->
<!-- from-green-400   → 開始色 -->
<!-- to-blue-500      → 終了色 -->
<div class="bg-gradient-to-r from-green-400 to-blue-500 p-4 text-white">
  緑から青へのグラデーション
</div>

<!-- 中間色を追加 -->
<div class="bg-gradient-to-r from-purple-400 via-pink-500 to-red-500 p-4 text-white">
  3色グラデーション
</div>

<!-- カスタム値 -->
<div class="bg-[linear-gradient(to_bottom,rgba(255,225,225,0)_70%,gold_30%)]">
  <!-- background-image: linear-gradient(#ffe1e100 70%, gold 30%); と出力される -->
  疑似的な下半分の黄色マーカースタイル
</div>
  • 背景画像(bg-ショートハンド)
<!-- bg-[url('/path/to/image.jpg')] → 背景画像を適用 -->
<!-- bg-no-repeat                   → 繰り返しなし -->
<!-- bg-center                      → background-position: center; -->
<!-- bg-cover                       → background-size: cover; -->
<div class="bg-[url('/path/to/image.jpg')] bg-no-repeat bg-center bg-cover">
  背景画像
</div>
<!-- bg-fixed   → 背景画像を固定 -->
<!-- bg-scroll  → スクロールと共に背景も動く -->
<div class="bg-fixed bg-[url('/mountain.jpg')] h-screen"></div>
<div class="bg-scroll bg-[url('/ocean.jpg')] h-screen"></div>
遷移アニメーション(transition
  • transition:デフォルトではall 150ms ease-out
<button class="bg-blue-500 text-white px-4 py-2 rounded transition hover:bg-blue-700">
  ホバーで色が変わる
</button>

<!-- transition-colors  → 色だけをスムーズに変更 -->
<!-- duration-500       → 遷移時間を 500ms に -->
<!-- ease-in-out        → ゆっくり開始&終了 -->
<button class="bg-blue-500 text-white px-4 py-2 rounded transition-colors duration-500 ease-in-out hover:bg-red-500">
  0.5秒かけて色を変える
</button>

<!-- transition-transform → transform のみアニメーション適用 -->
<!-- hover:scale-125      → ホバー時に 1.25 倍拡大 -->
<div class="w-20 h-20 bg-green-500 transition-transform duration-300 hover:scale-125">
  ホバーで拡大
</div>

<!-- hover:rotate-45 → ホバー時に 45 度回転 -->
<div class="w-20 h-20 bg-yellow-500 transition-transform duration-500 hover:rotate-45">
  ホバーで45度回転
</div>

<!-- hover:translate-x-10 → ホバー時に右へ 10(約 40px)移動 -->
<div class="w-20 h-20 bg-purple-500 transition-transform duration-300 hover:translate-x-10">
  ホバーで右に移動
</div>
  • transition + transform
<!-- transition-all       → すべての変更をアニメーション -->
<!-- hover:rotate-45      → 45度回転 -->
<!-- hover:scale-110      → 1.1倍に拡大 -->
<!-- hover:translate-x-5  → 右へ 5(約 20px)移動 -->
<div class="w-20 h-20 bg-red-500 transition-all duration-500 hover:rotate-45 hover:scale-110 hover:translate-x-5">
  ホバーで変形
</div>


アニメーション(@keyframes) | animation
  • @layer utilities

    カスタムの@keyframesを定義するには@layer {任意の名前}を使用する
@layer utilities {
  @keyframes fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  
  .animate-fade-in {
    animation: fade-in 1s ease-in-out;
  }
}

※その他、JavaScriptと連動して特定クラスの付与解除を行いたい場合にも重宝する。

@layer common {
  .modalElm {
    opacity: 0;
    visibility: hidden;

    &.onView {
      opacity: 1;
      visibility: visible;
    }
  }
}

  • tailwind.config.jsで設定
module.exports = {
  theme: {
    // 以下 extend 内に設定を記述していく
    extend: {
      animation: {
        'slide-in': 'slide-in 0.5s ease-out',
      },
      keyframes: {
        'slide-in': {
          '0%': { transform: 'translateX(-100%)', opacity: '0' },
          '100%': { transform: 'translateX(0)', opacity: '1' },
        },
      },
    },
  },
  plugins: [],
};
  • コンポーネントで設定したanimationを適用
<!-- 設定した animate-slide-in -->
<div class="animate-slide-in bg-blue-500 text-white p-4">
  スライドインする要素
</div>
  • animation-delayを適用
    []内にカスタムCSSを直接書くことで適用させる
<!-- [animation-delay:0.5s] → 0.5秒後にフェードイン -->
<div class="animate-fade-in [animation-delay:0.5s]">
  0.5秒後にフェードイン
</div>
ブレイクポイント(メディアクエリの指定方法)
  • Tailwind CSSのデフォルトブレイクポイントは以下で、カスタムメディアクエリmin-[カスタム値]:, max-[カスタム値]:)を使用する方法もあります。
- sm:  640  px以上の画面
- md:  768  px以上の画面
- lg:  1024 px以上の画面
- xl:  1280 px以上の画面
- 2xl: 1536 px以上の画面
<!-- 通常の画面ではテキストが小さく、lgサイズ(1024px)以上では大きく表示 -->
<div class="text-sm lg:text-lg">テキスト</div>

<!-- スマホでは列、PCでは行に並べる -->
<div class="flex flex-col md:flex-row">...</div>

<!-- カスタムメディアクエリを使用する方法 -->
<div class="min-[1025px]:text-[32px]">カスタムブレイクポイント</div>
<div class="max-[767px]:hidden">モバイルでは非表示</div>

擬似クラス・擬似要素・その他セレクタの指定方法

擬似クラス

checked:,disabled:など、ほとんどの擬似クラスは擬似クラス名:プレフィックスで対応します。

<button class="hover:bg-blue-700 focus:ring-2 active:bg-blue-800">ボタン</button>
<div class="first:pt-0 last:pb-0">リスト項目</div>
<li class="odd:bg-gray-100 even:bg-white">アイテム</li>

擬似要素

擬似要素はbefore:after:プレフィックスで対応します。
※対象ユーティリティクラスそれぞれに前置する必要があります。

<div class="before:content-['*'] before:text-red-500 before:mr-1 relative after:absolute after:content-[''] after:w-full after:h-[2px] after:bg-blue-500 after:bottom-0 after:left-0">
  疑似要素のテキスト
</div>

nth-child, nth-of-type

<ul>
  <!-- 2番目の項目だけ青色背景 -->
  <li class="nth-[2]:bg-blue-200">リスト項目</li> 
  <!-- 3の倍数の項目だけ緑色背景 -->
  <li class="nth-[3n]:bg-green-200">リスト項目</li>
  <!-- [Xn+1](例:3n+1 の場合 1,4,7,10,…)の項目だけ緑色背景 -->
  <li class="nth-[3n+1]:bg-green-200">リスト項目</li>
  <!-- nth-of-type を使った指定方法 -->
  <li class="nth-of-type-3:bg-green-400 nth-of-type-[3n+1]:bg-green-200">リスト項目</li>
</ul>

※あまり複雑なスタイル指定になるようでしたら@layerにバニラなCSSとして記述したほうが楽だと思います。

not()

not-:プレフィックスを使用

<!-- 一番先頭の要素以外 -->
<div class="not-first:mt-4">スタイルはあたらない</div>
<div class="not-first:mt-4">スタイル指定対象</div>
<div class="not-first:mt-4">スタイル指定対象</div>
<div class="not-first:mt-4">スタイル指定対象</div>

※あまり複雑なスタイル指定になるようでしたら@layerにバニラなCSSとして記述したほうが楽だと思います。

または、カスタム CSS と @apply ディレクティブを用いる。

/* src/css/app.css */
.not-third:not(:nth-child(3n)) {
  @apply bg-gray-100; /* 例:3番目以外の要素の背景色をグレーにする */
}

上記スタイルの適用例

<div class="not-third">要素 1</div>
<div class="not-third">要素 2</div>
<div class="not-third">要素 3</div>
<div class="not-third">要素 4</div>
<div class="not-third">要素 5</div>

:has()

<label
  class="has-checked:bg-indigo-50 has-checked:text-indigo-900 has-checked:ring-indigo-200"
>...

上記例には記載ないですがhas-[img], has-[a]というように要素を指定して使用できます。

データ属性と属性セレクタ

属性セレクタは[](角括弧)で表現する。

<details>
  <summary class="cursor-pointer">[open]を持つときのスタイル</summary>
  <div class="group-[[open]]:block hidden">詳細テキスト</div>
</details>

<!-- カスタムデータ属性 -->
<!-- data-[属性名=値]:クラス名 の形式でスタイルを適用 -->
<button data-state="active" class="data-[state=active]:bg-blue-500">アクティブボタン</button>
<!-- Will apply -->
<div data-active class="border border-gray-300 data-active:border-purple-500">
  <!-- ... -->
</div>
<!-- Will not apply -->
<div class="border border-gray-300 data-active:border-purple-500">
  <!-- ... -->
</div>

CSS in JSは避けるべき?

For new projects, I would not recommend adopting styled-components or most other css-in-js solutions.

記事冒頭にも転記した「新規プロジェクトにおいてCSS in JSは推奨されない」という部分ですが、記事内ではRSC(React Server Component)に少し触れていました。

実は、筆者は Next.js(v14, v15)の実務案件(※レンダリングモデルはSSG)でstyled-componentsを使っていたのですが、サイトの初期表示時の一瞬だけ大きくレイアウトが崩れていました。

今回の件で、それらstyled-componentsを使っていたものは全てCSS Modulesへ移行したのですが、移行後は先のような一瞬のレイアウト崩れもなく良好です。

ちなみに、CSS Modulesを選んだ理由は以下のように公式推奨で一番初めに記載されているためです。

https://nextjs.org/docs/app/building-your-application/styling/css-in-js

If you want to style Server Components, we recommend using CSS Modules or other solutions that output CSS files, like PostCSS or Tailwind CSS.

https://nextjs.org/docs/app/building-your-application/styling

以下キャプチャは上記ページのものですが、ここでも先頭はCSS Modulesですね。
そして、一番最後にCSS-in-JSが記載されています……。

ありがとう styled-components, よろしく Tailwind CSS

先の1フレームワークの情報だけを取り上げるのもですが、昨今の React のサーバー側への関心をはじめ、現状ではクライアントサイドで働くCSS-in-JSには厳しいものがあるのかもしれません。

React 制作の苦楽を教えてくれて、常に支えてくれたstyled-componentsには感謝しかありません。

そして、面倒だと思っていた移行作業も比較的軽く済んだ上に、学習コストもそこまでかからなかったTailwind CSS
これからよろしくおねがいします!

ここまで読んでいただき、ありがとうございました。

当記事がstyled-componentsからTailwind CSSへの移行を検討または実施されている方々のお役に立てれば幸いです。

Discussion