Skip to content

7.4 スタイリング — Tailwind / CSS Modules / styled-components ​

React 本体はスタイルの貼り方を規定していません。インラインの style 属性にオブジェクトを渡す書き方も、普通の CSS ファイルを import "./app.css" で読み込むのも、どちらも動きます。

tsx
<div style={{ color: "red" }}>Hello</div>

ただしアプリが大きくなると、「CSS のスコープ管理」「デザインシステムとの統合」「サーバーコンポーネント (RSC) 対応」などの課題が出てきて、何らかの仕組みが欲しくなります。

案件でよく出会う選択肢は 3 つです。

  1. Tailwind CSS — Utility First。HTML にクラスを並べるだけで済みます。
  2. CSS Modules — 素の CSS + 自動スコープ化。Vite にビルトインです。
  3. styled-components (CSS-in-JS) — JS の中に CSS を書きます。コンポーネントと 1:1 で対応します。

どれが「正解」というわけではなく、案件のデザインシステム、既存コード、チームの好み、RSC を使うかどうかで判断することになります。

図 7.4.1 スタイルを書く「場所」が 3 つのアプローチで違う ​

Tailwind CSSHTML の className に書く<h1 className= "text-3xl font-bold"/>ファイル切り替えなしCSS Modules別 CSS ファイルButton.module.css.primary { ... }Button.tsxclassName={styles.primary}素の CSS を書くstyled-componentsJS の中に書くconst Title = styled.h1` font-size: 1.5em; `;コンポーネントと 1:1

どこにスタイルを書くか、という設計判断の違いが、ぱっと見で伝わると思います。あとは「この書き方で自分のチームが運用していけるか」が決め手になります。

Tailwind CSS ​

Tailwind CSS (tailwindcss.com/docs) は、ユーティリティクラスを大量に提供する CSS フレームワークです。text-3xl、font-bold、bg-blue-500 のようなクラスを HTML に直接並べてスタイルを組み立てます。

Vite での導入 ​

公式の Vite 向けガイド (tailwindcss.com/docs/installation/using-vite) に沿った導入は次のようになります。

ts
// vite.config.ts
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [tailwindcss()],
});
css
/* src/index.css */
@import "tailwindcss";
tsx
function Title() {
  return <h1 className="text-3xl font-bold underline">Hello world!</h1>;
}

@import "tailwindcss" 一行で Tailwind のスタイル基盤を取り込めます。ビルド時に使用中のクラスだけが抽出されるため、本番 CSS は小さく保たれます(公式ガイドに「zero-runtime」「静的 CSS ファイルを生成」と明記されています)。

フィットする場面と、向かない場面 ​

フィットする場面:

  • デザインシステムが spacing-4 / color-blue-500 のようなトークン体系で整っている。Tailwind の仕組みと親和性が高いケースです。
  • スタイル命名の議論(button-primary か primary-button か)を避けたい。
  • RSC でも問題なく動きます(クライアントランタイムが要りません)。

向かない場面:

  • デザイン要件が複雑で、ユーティリティクラスが長大になり可読性が落ちる。
  • デザイナーが CSS ファイルで書きたいワークフロー。
  • チームが Tailwind 未経験で、学習コストを許容できない。

クラスが長くなる点はよく指摘される欠点ですが、@apply や小さなコンポーネントへの切り出しで緩和する運用が一般的です。

CSS Modules ​

CSS Modules は「CSS ファイルを書き、インポートしたクラス名が自動でローカルスコープになる」仕組みです。Vite はビルトインで対応しています (vite.dev/guide/features.html#css-modules)。

Vite の命名規則 ​

Vite は *.module.css(または .module.scss など)で終わるファイルを CSS Modules として扱います。インポートすると、クラス名 → スコープ付き識別子のマッピングオブジェクトが得られます。

css
/* Button.module.css */
.primary {
  background: blue;
  color: white;
}
tsx
// Button.tsx
import styles from "./Button.module.css";

export function Button() {
  return <button className={styles.primary}>送信</button>;
}

ビルド後、.primary は _primary_xxxxx_1 のようにハッシュ付きに変換されるため、別ファイルの .primary と衝突しません。

Vite の設定で css.modules.localsConvention: "camelCaseOnly" を指定すると、apply-color のようなケバブケースのクラス名を applyColor として名前付きインポートできます(公式 Guide より)。

フィットする場面と、向かない場面 ​

フィットする場面:

  • 素の CSS を書きたい。既存 CSS 資産を取り込みたい。
  • RSC でも問題なく使えます(ランタイム JS を足しません)。
  • 追加ライブラリを入れたくない。Vite だけで完結します。

向かない場面:

  • 小さな条件分岐(hover 時の色変えなど)を何度も書くのが面倒に感じる。
  • デザイントークンを JS 側から参照したい。

styled-components (CSS-in-JS) ​

styled-components (styled-components.com/docs) は「actual CSS code を書いてコンポーネントにバインドする」CSS-in-JS ライブラリです。タグ付きテンプレートリテラルで CSS を書きます。

最小例 ​

tsx
// styled-components.com/docs/basics のパターン
import styled from "styled-components";

const Title = styled.h1`
  font-size: 1.5em;
  color: #bf4f74;
`;

const Button = styled.button<{ $primary?: boolean }>`
  background: ${(props) => (props.$primary ? "#BF4F74" : "white")};
  color: ${(props) => (props.$primary ? "white" : "#BF4F74")};
  padding: 0.5em 1em;
  border: 2px solid #bf4f74;
`;

// 既存のコンポーネントを拡張する
const TomatoButton = styled(Button)`
  color: tomato;
`;

export function Page() {
  return (
    <>
      <Title>こんにちは</Title>
      <Button>通常</Button>
      <Button $primary>強調</Button>
    </>
  );
}

公式ドキュメントは「Define your styled components outside of the render method, otherwise it will be recreated on every single render pass」と注意しています。ファイル上部など コンポーネント外 で定義するのが定跡です。

フィットする場面と、向かない場面 ​

フィットする場面:

  • コンポーネントと CSS を 1 ファイル / 1 対にして凝集度を上げたい。
  • props ベースの動的スタイルを頻繁に書く(テーマや状態に応じて色を変える等)。

向かない場面:

  • React Server Components との相性: styled-components のランタイムは Client Component 側に配置するのが定跡 です。Next.js App Router で使う場合は、styled-components@6+ と、Next.js 公式ガイドの CSS-in-JS で紹介されている「Style Registry を Client Component で作って root layout をラップする」パターンを適用します。Server Component の JSX 内で直接 styled.xxx を呼ぶことはできないので、スタイルを使うコンポーネントは 'use client' 側に切り出す判断が必要です。
  • ランタイムコストを減らしたい場合(CSS 抽出型 / zero-runtime の CSS-in-JS を選ぶ余地があります。vanilla-extract、Linaria など)。

Emotion も styled-components と同系統の CSS-in-JS で、API が似ています。どちらを使うかは案件の選定に従いましょう。

RSC との相性 ​

Part 5 で扱った Server Components(5.2 Server vs Client Components)は、コンポーネントがサーバー上でレンダーされてクライアントには HTML(+ 必要最小限の JS)が届く仕組みです。このとき「クライアントで実行される JS」を持たないコンポーネントに、ランタイム CSS-in-JS を適用することはできません。

スタイリング手段RSC との相性
Tailwind CSS問題なし(ビルド時に静的 CSS を生成するため)
CSS Modules問題なし(同上)
グローバル CSS ファイル問題なし
styled-components(従来型ランタイム)Client Component でのみ使用可
Emotion(従来型ランタイム)同上
vanilla-extract / Linaria など zero-runtime 系ビルド時に CSS を抽出するため RSC で使える

図 7.4.2 RSC とスタイルの相性早見 ​

ビルド時に静的 CSS を生成→ RSC と素直に同居できるTailwind CSSCSS Modulesvanilla-extract / Linariaランタイム CSS-in-JS→ Client Component 側で使うstyled-components(従来型)Emotion(従来型)'use client' + Style Registry が必要

RSC 中心の構成(Next.js App Router)を選ぶなら、Tailwind か CSS Modules が第一候補になります。CSS-in-JS を使いたい場合は Client Component に区切りを入れるか、zero-runtime 系を選ぶ判断が必要です。

図 7.4.3 スタイリング選定フロー ​

参照ソース ​