スタイリング概要
この章では、Reactにおけるスタイリング手法の全体像を把握し、各アプローチの特徴と使い分けを学びます。
この章で学ぶこと
- Reactで使える5つのスタイリング手法の特徴
- 各手法のメリット・デメリット
- プロジェクトに適したスタイリング手法の選び方
clsxによるクラス名の効率的な管理
この章では03章で作成したプロジェクトを使用します。npm run devで開発サーバーを起動し、コードを書きながら学習を進めましょう。
Reactのスタイリング手法
Reactでは複数のスタイリング手法があり、プロジェクトの要件に応じて選択できます。
主なアプローチ
| 手法 | 特徴 | 代表的なツール |
|---|---|---|
| インラインスタイル | JSXに直接記述 | style属性 |
| CSSファイル | 従来のCSS | .css ファイル |
| CSS Modules | スコープ付きCSS | .module.css |
| CSS-in-JS | JSでスタイル定義 | 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-color → backgroundColor
試してみよう: インラインスタイルでボタンを作成
src/App.tsxで、青い背景・白い文字のボタンをインラインスタイルで作成してみましょう。
ヒント
style属性にオブジェクトを渡す- プロパティ名はキャメルケース(
backgroundColor等) - ホバー効果は
onMouseEnter/onMouseLeaveイベントで代替 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.cssとCard.tsxを作成し、カードコンポーネントをCSS Modulesでスタイリングしてみましょう。
ヒント
.module.cssファイルを作成import styles from './Card.module.css'でインポートclassName={styles.card}のように使用- ホバー効果も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 CSS、CSS 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)に対応したボタンコンポーネントを作成してください。
ヒント
npm install clsxでインストールimport clsx from 'clsx'でインポートclsx('class1', condition && 'class2')で条件付き結合- オブジェクト形式
{ '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: 高速開発、パフォーマンス良好(本書推奨)
clsxやtailwind-mergeでクラス名管理を効率化
次の章では、本書で採用するTailwind CSSについて詳しく学びます。