7.4 スタイリング — Tailwind / CSS Modules / styled-components
React 本体はスタイルの貼り方を規定していません。インラインの style 属性にオブジェクトを渡す書き方も、普通の CSS ファイルを import "./app.css" で読み込むのも、どちらも動きます。
<div style={{ color: "red" }}>Hello</div>ただしアプリが大きくなると、「CSS のスコープ管理」「デザインシステムとの統合」「サーバーコンポーネント (RSC) 対応」などの課題が出てきて、何らかの仕組みが欲しくなります。
案件でよく出会う選択肢は 3 つです。
- Tailwind CSS — Utility First。HTML にクラスを並べるだけで済みます。
- CSS Modules — 素の CSS + 自動スコープ化。Vite にビルトインです。
- styled-components (CSS-in-JS) — JS の中に CSS を書きます。コンポーネントと 1:1 で対応します。
どれが「正解」というわけではなく、案件のデザインシステム、既存コード、チームの好み、RSC を使うかどうかで判断することになります。
図 7.4.1 スタイルを書く「場所」が 3 つのアプローチで違う
どこにスタイルを書くか、という設計判断の違いが、ぱっと見で伝わると思います。あとは「この書き方で自分のチームが運用していけるか」が決め手になります。
Tailwind CSS
Tailwind CSS (tailwindcss.com/docs) は、ユーティリティクラスを大量に提供する CSS フレームワークです。text-3xl、font-bold、bg-blue-500 のようなクラスを HTML に直接並べてスタイルを組み立てます。
Vite での導入
公式の Vite 向けガイド (tailwindcss.com/docs/installation/using-vite) に沿った導入は次のようになります。
// vite.config.ts
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss()],
});/* src/index.css */
@import "tailwindcss";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 として扱います。インポートすると、クラス名 → スコープ付き識別子のマッピングオブジェクトが得られます。
/* Button.module.css */
.primary {
background: blue;
color: white;
}// 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 を書きます。
最小例
// 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 とスタイルの相性早見
RSC 中心の構成(Next.js App Router)を選ぶなら、Tailwind か CSS Modules が第一候補になります。CSS-in-JS を使いたい場合は Client Component に区切りを入れるか、zero-runtime 系を選ぶ判断が必要です。
図 7.4.3 スタイリング選定フロー
参照ソース
- Tailwind CSS — Installing using Vite
- Tailwind CSS — Documentation
- Vite — CSS Modules
- styled-components — Docs
- styled-components — Basics
- Next.js Docs — How to use CSS-in-JS libraries(styled-components を App Router で使う際の Style Registry パターン)
- 本サイト: 5.2 Server vs Client Components