メインコンテンツまでスキップ

スタイリング概要

この章では、Reactにおけるスタイリング手法の全体像を把握し、各アプローチの特徴と使い分けを学びます。

この章で学ぶこと

  • Reactで使える5つのスタイリング手法の特徴
  • 各手法のメリット・デメリット
  • プロジェクトに適したスタイリング手法の選び方
  • clsxによるクラス名の効率的な管理
備考

この章では03章で作成したプロジェクトを使用します。npm run devで開発サーバーを起動し、コードを書きながら学習を進めましょう。

Reactのスタイリング手法

Reactでは複数のスタイリング手法があり、プロジェクトの要件に応じて選択できます。

主なアプローチ

手法特徴代表的なツール
インラインスタイルJSXに直接記述style属性
CSSファイル従来のCSS.css ファイル
CSS Modulesスコープ付きCSS.module.css
CSS-in-JSJSでスタイル定義styled-components, Emotion
ユーティリティCSSクラスの組み合わせTailwind CSS

インラインスタイル

JSXのstyle属性にオブジェクトを渡す方法です。

function InlineStyleExample() {
const buttonStyle = {
backgroundColor: '#3b82f6',
color: 'white',
padding: '8px 16px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer'
}

return (
<button style={buttonStyle}>
クリック
</button>
)
}

特徴

  • メリット: JavaScriptの値を直接使用可能、コンポーネントと密結合
  • デメリット: 擬似クラス(:hover等)が使えない、パフォーマンス懸念
// 動的なスタイル
function DynamicStyle({ isActive }: { isActive: boolean }) {
return (
<div
style={{
backgroundColor: isActive ? '#22c55e' : '#ef4444',
transition: 'background-color 0.3s'
}}
>
{isActive ? 'アクティブ' : '非アクティブ'}
</div>
)
}
警告

インラインスタイルではプロパティ名がキャメルケースになります。background-colorbackgroundColor

試してみよう: インラインスタイルでボタンを作成

src/App.tsxで、青い背景・白い文字のボタンをインラインスタイルで作成してみましょう。

ヒント
  1. style属性にオブジェクトを渡す
  2. プロパティ名はキャメルケース(backgroundColor等)
  3. ホバー効果はonMouseEnter/onMouseLeaveイベントで代替
  4. React.CSSProperties型で型補完を有効に
回答と解説
// src/App.tsx
function App() {
const buttonStyle: React.CSSProperties = {
backgroundColor: '#3b82f6',
color: 'white',
padding: '8px 16px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer'
}

const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget.style.backgroundColor = '#2563eb'
}

const handleMouseLeave = (e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget.style.backgroundColor = '#3b82f6'
}

return (
<button
style={buttonStyle}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
クリック
</button>
)
}

export default App

インラインスタイルでは:hoverが使えないため、onMouseEnter/onMouseLeaveイベントで代替しています。React.CSSProperties型を指定すると、TypeScriptの補完が効きます。

CSSファイル(グローバルCSS)

従来のCSSファイルをインポートする方法です。

/* styles.css */
.button {
background-color: #3b82f6;
color: white;
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
}

.button:hover {
background-color: #2563eb;
}

.button--secondary {
background-color: #6b7280;
}
// Button.tsx
import './styles.css'

function Button({ variant = 'primary' }: { variant?: 'primary' | 'secondary' }) {
const className = variant === 'secondary'
? 'button button--secondary'
: 'button'

return <button className={className}>ボタン</button>
}

特徴

  • メリット: 慣れ親しんだCSS、フル機能(メディアクエリ、疑似クラス等)
  • デメリット: グローバルスコープ、名前衝突の可能性

CSS Modules

CSSファイルをモジュール化し、クラス名のスコープを自動的に限定する方法です。

/* Button.module.css */
.button {
background-color: #3b82f6;
color: white;
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
}

.button:hover {
background-color: #2563eb;
}

.secondary {
background-color: #6b7280;
}

.secondary:hover {
background-color: #4b5563;
}
// Button.tsx
import styles from './Button.module.css'

type ButtonProps = {
variant?: 'primary' | 'secondary'
children: React.ReactNode
}

function Button({ variant = 'primary', children }: ButtonProps) {
const className = variant === 'secondary'
? `${styles.button} ${styles.secondary}`
: styles.button

return <button className={className}>{children}</button>
}

特徴

  • メリット: スコープ付き(名前衝突なし)、既存のCSS知識が活かせる
  • デメリット: 動的なスタイリングが煩雑

コンパイル後のクラス名

<!-- ビルド後のHTML -->
<button class="Button_button_x7ht2 Button_secondary_a3fk9">
ボタン
</button>

試してみよう: CSS Modulesでカードを作成

src/components/Card.module.cssCard.tsxを作成し、カードコンポーネントをCSS Modulesでスタイリングしてみましょう。

ヒント
  1. .module.cssファイルを作成
  2. import styles from './Card.module.css'でインポート
  3. className={styles.card}のように使用
  4. ホバー効果もCSSで記述可能
回答と解説
/* src/components/Card.module.css */
.card {
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
background-color: white;
}

.title {
font-size: 18px;
font-weight: bold;
margin-bottom: 8px;
}

.content {
color: #666;
}

.card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
// src/components/Card.tsx
import styles from './Card.module.css'

type CardProps = {
title: string
children: React.ReactNode
}

function Card({ title, children }: CardProps) {
return (
<div className={styles.card}>
<h2 className={styles.title}>{title}</h2>
<div className={styles.content}>{children}</div>
</div>
)
}

export default Card
// src/App.tsx での使用例
import Card from './components/Card'

function App() {
return (
<Card title="カードタイトル">
カードの内容がここに入ります。
</Card>
)
}

CSS Modulesは.module.cssという拡張子を使うことで、Viteが自動的にクラス名をスコープ化します。styles.cardのようにオブジェクトのプロパティとしてアクセスします。

CSS-in-JS

JavaScriptでスタイルを定義する手法です。styled-componentsやEmotionが代表的です。

styled-components

import styled from 'styled-components'

const Button = styled.button<{ $variant?: 'primary' | 'secondary' }>`
background-color: ${props =>
props.$variant === 'secondary' ? '#6b7280' : '#3b82f6'};
color: white;
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;

&:hover {
background-color: ${props =>
props.$variant === 'secondary' ? '#4b5563' : '#2563eb'};
}
`

// 使用例
function App() {
return (
<>
<Button>プライマリ</Button>
<Button $variant="secondary">セカンダリ</Button>
</>
)
}

Emotion

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'

const buttonStyle = (variant: 'primary' | 'secondary') => css`
background-color: ${variant === 'secondary' ? '#6b7280' : '#3b82f6'};
color: white;
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;

&:hover {
background-color: ${variant === 'secondary' ? '#4b5563' : '#2563eb'};
}
`

function Button({ variant = 'primary' }: { variant?: 'primary' | 'secondary' }) {
return <button css={buttonStyle(variant)}>ボタン</button>
}

特徴

  • メリット: 動的スタイリングが容易、スコープ付き、コンポーネントと密結合
  • デメリット: ランタイムオーバーヘッド、バンドルサイズ増加、React Server Components との相性が悪い
警告

CSS-in-JS に関する注意点

  • React Server Components との互換性問題: styled-components や Emotion のような「ランタイム CSS-in-JS」は、サーバー側でのレンダリングと相性が悪く、Next.js App Router での利用に追加対応が必要になります。2022 年には Emotion のメンテナーが CSS-in-JS からの離脱を表明し、2025 年 3 月には styled-components 自身が OpenCollective でメンテナンスモード入りを発表し、メンテナーが「新規プロジェクトでの採用は推奨しない」と明言しています。React チームもランタイム CSS-in-JS と RSC の相性問題を指摘しています。
  • ランタイムコスト: 毎レンダーでスタイル文字列を生成するため、パフォーマンスへの影響が無視できません。React Compiler による自動メモ化でも回避できないコストです。
  • 推奨される代替: 新規プロジェクトでは Tailwind CSSCSS Modules、もしくはビルド時に静的 CSS を生成するタイプの CSS-in-JS(vanilla-extract、Panda CSS、Linaria など)が推奨されます。
  • 既存プロジェクト: すでに styled-components / Emotion を使っている場合、性能が問題になっていなければ無理に移行する必要はありません。ただし React Server Components を採用する時点で別の選択肢を検討すべきです。

ユーティリティCSS(Tailwind CSS)

あらかじめ定義されたユーティリティクラスを組み合わせてスタイリングする手法です。

function Button({ variant = 'primary' }: { variant?: 'primary' | 'secondary' }) {
const baseClasses = 'px-4 py-2 rounded-sm border-none cursor-pointer transition-colors'
const variantClasses = variant === 'secondary'
? 'bg-gray-500 text-white hover:bg-gray-600'
: 'bg-blue-500 text-white hover:bg-blue-600'

return (
<button className={`${baseClasses} ${variantClasses}`}>
ボタン
</button>
)
}

特徴

  • メリット: 高速な開発、一貫したデザインシステム、CSSファイル不要
  • デメリット: クラス名が長くなる、学習コスト
備考

Tailwind CSSについては次の章で詳しく解説します。

スタイリング手法の比較

パフォーマンス

手法ランタイムバンドルサイズ
CSS Modulesなし
Tailwind CSSなし小(使用クラスのみ生成される)
styled-componentsあり
インラインスタイルあり

開発体験

手法TypeScript対応動的スタイルエディタ補完
CSS Modules
Tailwind CSS
CSS-in-JS
インラインスタイル

選定ガイドライン

Tailwind CSSがおすすめ

  • 迅速なUI開発が必要
  • デザインシステムの一貫性を重視
  • パフォーマンスを重視
  • 本書のメイン推奨

CSS Modulesがおすすめ

  • 既存のCSSスキルを活かしたい
  • シンプルなプロジェクト
  • フレームワーク非依存を重視

CSS-in-JSがおすすめ

  • 高度な動的スタイリングが必要
  • コンポーネントライブラリを開発
  • テーマ切り替えが頻繁

classNameの結合ユーティリティ

複数のクラス名を条件付きで結合する場合、ユーティリティライブラリが使えます。

clsx

npm install clsx
import clsx from 'clsx'

type ButtonProps = {
variant?: 'primary' | 'secondary'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
}

function Button({ variant = 'primary', size = 'md', disabled }: ButtonProps) {
return (
<button
className={clsx(
// ベースクラス
'rounded-sm font-medium transition-colors',
// サイズ
{
'px-2 py-1 text-sm': size === 'sm',
'px-4 py-2': size === 'md',
'px-6 py-3 text-lg': size === 'lg'
},
// バリアント
{
'bg-blue-500 text-white hover:bg-blue-600': variant === 'primary',
'bg-gray-500 text-white hover:bg-gray-600': variant === 'secondary'
},
// 状態
disabled && 'opacity-50 cursor-not-allowed'
)}
disabled={disabled}
>
ボタン
</button>
)
}

tailwind-merge(Tailwind CSS専用)

Tailwind CSSでクラスの衝突を解決します。

npm install tailwind-merge
import { twMerge } from 'tailwind-merge'

// 後のクラスが優先される
twMerge('px-2 py-1', 'px-4')
// => 'py-1 px-4'

// clsxと組み合わせて使用
import clsx from 'clsx'

function cn(...inputs: Parameters<typeof clsx>) {
return twMerge(clsx(inputs))
}

試してみよう: clsxを導入

npm install clsxでインストールし、条件付きクラス名の結合を試してみましょう。バリアント(primary/secondary/danger)とサイズ(sm/md/lg)に対応したボタンコンポーネントを作成してください。

ヒント
  1. npm install clsxでインストール
  2. import clsx from 'clsx'でインポート
  3. clsx('class1', condition && 'class2')で条件付き結合
  4. オブジェクト形式{ 'class': condition }も使用可能
回答と解説
npm install clsx
// src/components/Button.tsx
import clsx from 'clsx'

type ButtonProps = {
variant?: 'primary' | 'secondary' | 'danger'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
children: React.ReactNode
}

function Button({
variant = 'primary',
size = 'md',
disabled = false,
children
}: ButtonProps) {
// Tailwind CSSと組み合わせる例
return (
<button
className={clsx(
'rounded-sm font-bold transition-colors',
{
'px-2 py-1 text-xs': size === 'sm',
'px-4 py-2 text-sm': size === 'md',
'px-6 py-3 text-base': size === 'lg'
},
{
'bg-blue-500 text-white hover:bg-blue-600': variant === 'primary',
'bg-gray-500 text-white hover:bg-gray-600': variant === 'secondary',
'bg-red-500 text-white hover:bg-red-600': variant === 'danger'
},
disabled && 'opacity-50 cursor-not-allowed'
)}
disabled={disabled}
>
{children}
</button>
)
}

export default Button

clsxは条件付きでクラス名を結合するユーティリティです。文字列、オブジェクト、配列を混在させて記述でき、falsy な値は自動的に除外されます。Tailwind CSSとの相性が特に良く、複雑なバリアント管理が簡潔に書けます。

まとめ

  • Reactには複数のスタイリング手法があり、プロジェクトに応じて選択
  • インラインスタイル: 動的だが機能制限あり
  • CSS Modules: スコープ付きで安全
  • CSS-in-JS: 柔軟だがランタイムコストあり
  • Tailwind CSS: 高速開発、パフォーマンス良好(本書推奨)
  • clsxtailwind-mergeでクラス名管理を効率化

次の章では、本書で採用するTailwind CSSについて詳しく学びます。

次に読む