Emotion と Tailwind CSS は共存できる?実例付き解説

モダンな React 開発において、スタイリングの選択肢は多様化しています。CSS-in-JS の代表格である Emotion と、ユーティリティファーストの Tailwind CSS、どちらも素晴らしい技術ですが、「両方を一つのプロジェクトで使いたい」という声をよく耳にします。
実際のところ、Emotion と Tailwind CSS は共存できるのでしょうか。結論から言うと、はい、適切な設定と戦略があれば、両者を効果的に併用することが可能です。
本記事では、技術的な検証を通じて、なぜ併用が有効なのか、どのような課題があるのか、そして実際にどう実装すれば良いのかを詳しく解説いたします。両技術の特性を理解し、適材適所で活用することで、より柔軟で効率的な開発体験を手に入れることができるでしょう。
背景
CSS-in-JS(Emotion)の特徴とメリット
Emotion は React における CSS-in-JS ライブラリの中でも、特に人気が高い技術です。JavaScript の中に CSS を記述することで、動的なスタイリングを簡単に実現できます。
javascriptimport { css } from '@emotion/react'
const buttonStyle = css`
background-color: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
&:hover {
opacity: 0.8;
}
`
このアプローチにより、以下のようなメリットを享受できます:
# | メリット | 詳細 |
---|---|---|
1 | 動的スタイリング | Props や state に基づいた条件付きスタイリングが直感的 |
2 | スコープ分離 | コンポーネント単位でスタイルが分離され、予期しない影響を防げる |
3 | JavaScript 連携 | 変数、関数、ループなどの JavaScript 機能をフル活用可能 |
4 | 型安全性 | TypeScript との相性が良く、型チェックが効く |
typescriptinterface ButtonProps {
variant: 'primary' | 'secondary' | 'danger'
size: 'small' | 'medium' | 'large'
children: React.ReactNode
}
const Button: React.FC<ButtonProps> = ({ variant, size, children }) => {
return (
<button css={getButtonStyles(variant, size)}>
{children}
</button>
)
}
ユーティリティファースト(Tailwind CSS)の特徴とメリット
一方、Tailwind CSS はユーティリティファーストの設計思想に基づいています。予め定義されたクラス名を組み合わせることで、迅速にスタイリングを行うことができます。
html<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
ボタン
</button>
Tailwind CSS の主なメリットには以下があります:
# | メリット | 詳細 |
---|---|---|
1 | 高速開発 | 事前定義されたクラスで素早くスタイリングが可能 |
2 | 一貫性確保 | デザインシステムに基づいた統一されたスタイリング |
3 | 小さなバンドル | PurgeCSS により未使用スタイルを自動削除 |
4 | 学習コストの軽減 | CSS を直接書かずに済む場合が多い |
両者の設計思想の違い
Emotion と Tailwind CSS は、根本的に異なる設計思想を持っています。この違いを理解することで、併用の意義がより明確になります。
以下の図は、両技術のアプローチの違いを示しています:
mermaidflowchart TB
subgraph emotion ["Emotion アプローチ"]
js[JavaScript コード] --> css_in_js[CSS-in-JS]
css_in_js --> dynamic[動的スタイル生成]
dynamic --> component[コンポーネント固有スタイル]
end
subgraph tailwind ["Tailwind CSS アプローチ"]
html[HTML/JSX] --> utility[ユーティリティクラス]
utility --> predefined[事前定義スタイル]
predefined --> consistent[一貫したデザイン]
end
emotion -.->|補完関係| tailwind
tailwind -.->|補完関係| emotion
この図が示すように、Emotion は動的で柔軟なスタイリングを得意とし、Tailwind CSS は一貫性と効率性を重視しています。両者は競合するのではなく、むしろ補完的な関係にあると言えるでしょう。
図で理解できる要点:
- Emotion:動的性と柔軟性を重視
- Tailwind CSS:一貫性と効率性を重視
- 両者は異なる強みを持つ補完的な技術
課題
併用時に発生する問題点
Emotion と Tailwind CSS を併用する際には、いくつかの技術的課題が生じます。これらの課題を事前に理解しておくことで、適切な対策を講じることができます。
スタイルの競合リスク
最も重要な課題は、スタイルの競合です。同じ要素に対して、Emotion と Tailwind CSS の両方からスタイルが適用された場合、CSS の優先順位によって予期しない結果が生じる可能性があります。
以下のコード例で問題を確認してみましょう:
typescriptimport { css } from '@emotion/react'
// Emotion で定義したスタイル
const customButton = css`
background-color: #ff6b6b;
padding: 1rem 2rem;
font-size: 1.2rem;
`
// 問題のあるコンポーネント例
const ProblematicButton = () => {
return (
<button
css={customButton}
className="bg-blue-500 p-4 text-lg"
>
どちらのスタイルが適用される?
</button>
)
}
この例では、背景色で競合が発生します:
- Emotion:
background-color: #ff6b6b
(赤系) - Tailwind:
bg-blue-500
(青系)
バンドルサイズの増大
両方のライブラリを導入することで、バンドルサイズが増大する懸念があります。特に、適切な最適化を行わない場合、以下のような問題が発生する可能性があります:
javascript// 問題のある設定例
module.exports = {
// Tailwind CSS の PurgeCSS 設定が不十分
purge: ['./src/**/*.{js,jsx,ts,tsx}'], // Emotion のスタイルを考慮していない
// Emotion の設定も同時に存在
babel: {
plugins: ['@emotion/babel-plugin']
}
}
開発体験の複雑化
チーム開発において、どちらの技術をいつ使うかの判断基準が曖昧だと、以下のような問題が生じます:
typescript// 統一感のないコード例
const InconsistentComponent = () => {
// 一部は Tailwind
const headerStyle = "text-2xl font-bold text-gray-800"
// 一部は Emotion
const bodyStyle = css`
color: #333;
line-height: 1.6;
margin-top: 1rem;
`
return (
<div>
<h2 className={headerStyle}>タイトル</h2>
<p css={bodyStyle}>本文テキスト</p>
</div>
)
}
既存プロジェクトでの移行の難しさ
既存のプロジェクトに新しいスタイリング手法を導入する際の課題も重要です。特に以下のようなシナリオでは、慎重な計画が必要になります:
mermaidflowchart LR
legacy[既存コードベース] --> emotion_only[Emotion のみ]
legacy --> tailwind_only[Tailwind CSS のみ]
legacy --> mixed[混在状態]
emotion_only --> migration1[Tailwind 導入]
tailwind_only --> migration2[Emotion 導入]
mixed --> optimize[最適化フェーズ]
migration1 --> challenges1[設定競合]
migration2 --> challenges2[型定義衝突]
optimize --> challenges3[パフォーマンス調整]
移行時の主な課題:
- 既存コンポーネントとの整合性確保
- 段階的移行戦略の策定
- チームメンバーへの教育とガイドライン整備
これらの課題を理解した上で、次章では具体的な解決策を検討していきましょう。
解決策
併用を実現する3つのアプローチ
Emotion と Tailwind CSS の併用課題を解決するために、3つの主要なアプローチをご紹介します。それぞれの特徴と適用場面を詳しく見ていきましょう。
スコープ分離によるアプローチ
最も安全で管理しやすいアプローチは、明確な役割分担によるスコープ分離です。このアプローチでは、使用場面を明確に分けることで競合を回避します。
以下の図は、スコープ分離の概念を示しています:
mermaidflowchart TB
subgraph app [アプリケーション全体]
subgraph layout [レイアウト・基盤]
tailwind_area[Tailwind CSS 担当]
tailwind_area --> grid[グリッドシステム]
tailwind_area --> spacing[余白・サイズ]
tailwind_area --> colors[基本カラー]
end
subgraph components [コンポーネント]
emotion_area[Emotion 担当]
emotion_area --> dynamic[動的スタイル]
emotion_area --> complex[複雑なアニメーション]
emotion_area --> conditional[条件付きスタイル]
end
end
具体的な実装例を見てみましょう:
typescript// Tailwind CSS: レイアウトと基本スタイル
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div className="min-h-screen bg-gray-50">
<header className="bg-white shadow-sm border-b border-gray-200 px-6 py-4">
<nav className="max-w-7xl mx-auto flex justify-between items-center">
{children}
</nav>
</header>
</div>
)
}
typescript// Emotion: 動的で複雑なコンポーネントスタイル
import { css } from '@emotion/react'
interface AnimatedButtonProps {
variant: 'primary' | 'secondary'
isLoading: boolean
progress?: number
}
const getAnimatedButtonStyle = (props: AnimatedButtonProps) => css`
position: relative;
overflow: hidden;
transition: all 0.3s ease;
${props.isLoading && `
pointer-events: none;
opacity: 0.7;
`}
${props.progress !== undefined && `
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: ${props.progress}%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3));
transition: width 0.3s ease;
}
`}
`
段階的移行アプローチ
既存プロジェクトでは、段階的な移行が現実的です。このアプローチでは、新しいコンポーネントから徐々に併用パターンを導入します。
typescript// フェーズ1: 新規コンポーネントでの併用
const NewFeatureCard: React.FC = () => {
return (
<div className="p-6 bg-white rounded-lg shadow-md"> {/* Tailwind: 基本レイアウト */}
<button css={dynamicButtonStyle}> {/* Emotion: 動的要素 */}
新機能
</button>
</div>
)
}
typescript// フェーズ2: 既存コンポーネントの段階的更新
const ExistingComponent: React.FC = () => {
// 既存の Emotion スタイルを維持
const legacyStyle = css`
/* 既存の複雑なスタイル */
`
return (
<div css={legacyStyle}>
{/* 新しい子要素には Tailwind を使用 */}
<div className="mt-4 flex gap-2">
<button className="btn-primary">新しいボタン</button>
</div>
</div>
)
}
ハイブリッド活用アプローチ
最も柔軟なアプローチは、両者の強みを組み合わせるハイブリッド活用です。Tailwind CSS で基盤を作り、Emotion で細かな調整を行います。
typescriptimport { css } from '@emotion/react'
import cn from 'classnames'
interface SmartButtonProps {
baseStyle?: 'primary' | 'secondary'
customStyle?: any
children: React.ReactNode
}
const SmartButton: React.FC<SmartButtonProps> = ({
baseStyle = 'primary',
customStyle,
children
}) => {
// Tailwind CSS で基本スタイルを定義
const baseClasses = cn({
'px-4 py-2 rounded-md font-medium transition-colors': true,
'bg-blue-600 text-white hover:bg-blue-700': baseStyle === 'primary',
'bg-gray-200 text-gray-800 hover:bg-gray-300': baseStyle === 'secondary'
})
return (
<button
className={baseClasses}
css={customStyle} // Emotion で追加カスタマイズ
>
{children}
</button>
)
}
使用例:
typescriptconst ExampleUsage = () => {
const specialButtonStyle = css`
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
transform: perspective(1px) translateZ(0);
&:hover {
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
`
return (
<SmartButton
baseStyle="primary"
customStyle={specialButtonStyle}
>
特別なボタン
</SmartButton>
)
}
設定ファイルの最適化
併用を成功させるためには、適切な設定が不可欠です。以下に最適化された設定例を示します:
javascript// tailwind.config.js
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
// Emotion で動的に生成されるクラス名も考慮
"./src/**/*.emotion.{js,jsx,ts,tsx}"
],
// 競合を避けるためのプレフィックス設定
prefix: 'tw-',
theme: {
extend: {
// カスタムカラーは CSS 変数として定義
colors: {
primary: 'var(--color-primary)',
secondary: 'var(--color-secondary)'
}
}
}
}
typescript// emotion.config.ts
import { css } from '@emotion/react'
// Tailwind CSS の値を参照する Emotion スタイル
export const emotionWithTailwind = css`
color: var(--color-primary);
padding: theme('spacing.4');
border-radius: theme('borderRadius.md');
`
これらのアプローチを適切に選択することで、Emotion と Tailwind CSS の併用による課題を効果的に解決できます。次章では、具体的な実装例を詳しく見ていきましょう。
具体例
Next.js での実装例
Next.js は React アプリケーションの構築において最も人気のあるフレームワークの一つです。ここでは、Next.js 環境で Emotion と Tailwind CSS を併用する実装例を詳しく解説いたします。
プロジェクトセットアップ
まず、必要なパッケージをインストールします:
bashyarn add @emotion/react @emotion/styled @emotion/css
yarn add tailwindcss postcss autoprefixer
yarn add -D @emotion/babel-plugin @types/react
Next.js 設定ファイル
javascript// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
// App Router での Emotion サポート
appDir: true,
},
compiler: {
// Emotion の設定
emotion: {
sourceMap: true,
autoLabel: 'dev-only',
labelFormat: '[local]',
},
},
}
module.exports = nextConfig
Babel 設定
javascript// .babelrc.js
module.exports = {
presets: [
[
'next/babel',
{
'preset-react': {
runtime: 'automatic',
importSource: '@emotion/react',
},
},
],
],
plugins: ['@emotion/babel-plugin'],
}
Tailwind CSS 設定
javascript// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
// Emotion と連携しやすいカスタムプロパティ
colors: {
primary: {
50: 'var(--color-primary-50)',
500: 'var(--color-primary-500)',
900: 'var(--color-primary-900)',
},
},
},
},
plugins: [],
}
グローバルスタイル設定
css/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--color-primary-50: #eff6ff;
--color-primary-500: #3b82f6;
--color-primary-900: #1e3a8a;
}
実際のコンポーネント実装
以下は、Next.js アプリケーションでの実践的なコンポーネント例です:
typescript// components/ProductCard.tsx
'use client'
import { css } from '@emotion/react'
import { useState } from 'react'
interface Product {
id: number
name: string
price: number
image: string
inStock: boolean
}
interface ProductCardProps {
product: Product
onAddToCart: (productId: number) => void
}
const ProductCard: React.FC<ProductCardProps> = ({ product, onAddToCart }) => {
const [isHovered, setIsHovered] = useState(false)
// Emotion: 動的なホバー効果とアニメーション
const cardAnimation = css`
transform: ${isHovered ? 'translateY(-8px)' : 'translateY(0)'};
box-shadow: ${isHovered
? '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)'
: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)'
};
transition: all 0.3s ease;
`
const priceStyle = css`
color: ${product.inStock ? 'var(--color-primary-500)' : '#ef4444'};
font-weight: ${product.inStock ? '600' : '400'};
text-decoration: ${product.inStock ? 'none' : 'line-through'};
`
return (
<div
className="bg-white rounded-xl p-6 max-w-sm mx-auto" // Tailwind: 基本レイアウト
css={cardAnimation} // Emotion: 動的アニメーション
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* Tailwind CSS: 画像とレイアウト */}
<div className="relative overflow-hidden rounded-lg mb-4">
<img
src={product.image}
alt={product.name}
className="w-full h-48 object-cover"
/>
{!product.inStock && (
<div className="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<span className="text-white font-semibold">在庫切れ</span>
</div>
)}
</div>
{/* Tailwind CSS: テキストレイアウト */}
<h3 className="text-xl font-bold text-gray-800 mb-2">
{product.name}
</h3>
{/* Emotion: 動的価格スタイル */}
<p css={priceStyle} className="text-2xl mb-4">
¥{product.price.toLocaleString()}
</p>
{/* Tailwind CSS: ボタン基本スタイル + Emotion: 動的状態 */}
<button
onClick={() => onAddToCart(product.id)}
disabled={!product.inStock}
className={`
w-full py-3 px-4 rounded-lg font-semibold transition-colors
${product.inStock
? 'bg-blue-600 hover:bg-blue-700 text-white'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}
`}
>
{product.inStock ? 'カートに追加' : '在庫なし'}
</button>
</div>
)
}
export default ProductCard
ページコンポーネントでの使用例
typescript// app/products/page.tsx
'use client'
import { css } from '@emotion/react'
import ProductCard from '@/components/ProductCard'
const productsData = [
{ id: 1, name: 'ワイヤレスイヤホン', price: 15800, image: '/images/earphone.jpg', inStock: true },
{ id: 2, name: 'スマートウォッチ', price: 45000, image: '/images/watch.jpg', inStock: false },
]
const ProductsPage = () => {
// Emotion: ページ固有のアニメーション
const pageAnimation = css`
opacity: 0;
animation: fadeInUp 0.6s ease-out forwards;
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`
const handleAddToCart = (productId: number) => {
console.log(`商品 ${productId} をカートに追加しました`)
}
return (
<div css={pageAnimation}>
{/* Tailwind CSS: ページレイアウト */}
<div className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold text-center text-gray-800 mb-12">
おすすめ商品
</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{productsData.map((product) => (
<ProductCard
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</div>
</div>
</div>
)
}
export default ProductsPage
Vite + React での実装例
Vite は高速な開発環境を提供する現代的なビルドツールです。React との組み合わせでも Emotion と Tailwind CSS の併用が可能です。
Vite 設定
javascript// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
jsxImportSource: '@emotion/react',
babel: {
plugins: ['@emotion/babel-plugin'],
},
}),
],
css: {
postcss: './postcss.config.js',
},
})
PostCSS 設定
javascript// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
TypeScript 設定
json{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react"
},
"types": ["@emotion/react/types/css-prop"]
}
React コンポーネント例
typescript// src/components/Dashboard.tsx
import { css } from '@emotion/react'
import { useState, useEffect } from 'react'
interface MetricCardProps {
title: string
value: number
change: number
icon: string
}
const MetricCard: React.FC<MetricCardProps> = ({ title, value, change, icon }) => {
const [isVisible, setIsVisible] = useState(false)
useEffect(() => {
const timer = setTimeout(() => setIsVisible(true), 100)
return () => clearTimeout(timer)
}, [])
// Emotion: 複雑なアニメーションとグラデーション
const cardStyle = css`
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
transform: ${isVisible ? 'translateY(0) scale(1)' : 'translateY(20px) scale(0.9)'};
opacity: ${isVisible ? 1 : 0};
transition: all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, transparent 100%);
border-radius: inherit;
}
`
const changeStyle = css`
color: ${change >= 0 ? '#10b981' : '#ef4444'};
&::before {
content: '${change >= 0 ? '↗' : '↘'}';
margin-right: 4px;
}
`
return (
<div
className="relative p-6 rounded-xl text-white shadow-lg" // Tailwind: 基本スタイル
css={cardStyle} // Emotion: 動的アニメーション
>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold opacity-90">{title}</h3>
<span className="text-2xl">{icon}</span>
</div>
<div className="space-y-2">
<p className="text-3xl font-bold">{value.toLocaleString()}</p>
<p css={changeStyle} className="text-sm font-medium">
{Math.abs(change)}%
</p>
</div>
</div>
)
}
const Dashboard = () => {
const metrics = [
{ title: 'Total Users', value: 12534, change: 12.5, icon: '👥' },
{ title: 'Revenue', value: 8429, change: -2.1, icon: '💰' },
{ title: 'Orders', value: 1543, change: 8.2, icon: '📦' },
]
return (
<div className="min-h-screen bg-gray-50 p-8">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold text-gray-800 mb-8">ダッシュボード</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{metrics.map((metric, index) => (
<MetricCard key={index} {...metric} />
))}
</div>
</div>
</div>
)
}
export default Dashboard
設定ファイルの詳細解説
パッケージ.json の最適化
json{
"name": "emotion-tailwind-app",
"dependencies": {
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"tailwindcss": "^3.3.0",
"react": "^18.2.0"
},
"devDependencies": {
"@emotion/babel-plugin": "^11.11.0",
"@types/react": "^18.2.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0"
},
"scripts": {
"dev": "next dev",
"build": "next build && yarn analyze-bundle",
"analyze-bundle": "npx @next/bundle-analyzer"
}
}
開発環境の最適化設定
typescript// .eslintrc.js
module.exports = {
extends: ['next/core-web-vitals'],
rules: {
// Emotion の css prop を許可
'react/no-unknown-property': ['error', { ignore: ['css'] }],
},
}
typescript// tsconfig.json
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "@emotion/react"
},
"types": ["@emotion/react/types/css-prop"]
}
これらの実装例により、Next.js と Vite の両環境で Emotion と Tailwind CSS を効果的に併用する方法をご理解いただけたでしょう。各フレームワークの特性を活かしながら、両技術の強みを最大限に引き出すことができます。
まとめ
併用のメリット・デメリット総括
Emotion と Tailwind CSS の併用について、技術的な検証を通じて詳しく解説してまいりました。ここで、両技術を併用することのメリットとデメリットを総括いたします。
併用のメリット
# | メリット | 詳細解説 |
---|---|---|
1 | 開発効率の向上 | Tailwind CSS で素早くレイアウトを構築し、Emotion で細かな調整を実現 |
2 | 柔軟性の確保 | 静的なデザインシステムと動的なスタイリングの両方を活用可能 |
3 | 段階的移行 | 既存プロジェクトに新しい技術を段階的に導入できる |
4 | チーム開発の最適化 | スキルレベルに応じて適切な技術を選択できる |
5 | 保守性の向上 | 役割分担により、コードの可読性と保守性が向上 |
併用のデメリット
# | デメリット | 対策 |
---|---|---|
1 | 学習コストの増加 | 段階的な学習計画と明確なガイドライン策定 |
2 | バンドルサイズの増大 | 適切な最適化設定とTree Shakingの活用 |
3 | 設定の複雑化 | 標準化されたテンプレートの使用 |
4 | スタイル競合のリスク | 明確な使い分けルールの策定 |
推奨される使い分け指針
実際の開発における効果的な使い分け指針をご提案いたします:
Tailwind CSS を使用すべき場面
typescript// ✅ レイアウトとユーティリティスタイル
const Layout = () => (
<div className="container mx-auto px-4 py-8 grid grid-cols-1 lg:grid-cols-3 gap-6">
<main className="lg:col-span-2 space-y-6">
{/* メインコンテンツ */}
</main>
<aside className="bg-white rounded-lg shadow-md p-6">
{/* サイドバー */}
</aside>
</div>
)
// ✅ 基本的なコンポーネントスタイル
const Button = ({ variant = 'primary', children }) => (
<button className={`
px-4 py-2 rounded-md font-medium transition-colors
${variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700' : ''}
${variant === 'secondary' ? 'bg-gray-200 text-gray-800 hover:bg-gray-300' : ''}
`}>
{children}
</button>
)
Emotion を使用すべき場面
typescript// ✅ 動的で複雑なスタイリング
const AnimatedCard = ({ isExpanded, progress }) => {
const cardStyle = css`
transform: scale(${isExpanded ? 1.05 : 1});
box-shadow: ${isExpanded ? '0 20px 40px rgba(0,0,0,0.1)' : '0 4px 8px rgba(0,0,0,0.05)'};
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: ${progress}%;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.1));
transition: width 0.5s ease;
}
`
return <div css={cardStyle}>{/* カードコンテンツ */}</div>
}
// ✅ テーマやプロップスベースのスタイリング
const ThemedComponent = ({ theme, size }) => {
const themeStyles = css`
color: ${theme.primaryColor};
background: ${theme.backgroundColor};
font-size: ${size === 'large' ? '1.5rem' : '1rem'};
&:hover {
background: ${theme.hoverColor};
}
`
return <div css={themeStyles}>{/* コンテンツ */}</div>
}
ハイブリッド活用の推奨パターン
typescript// ✅ 基本は Tailwind、カスタマイズは Emotion
const SmartComponent = ({ status, customAnimation }) => {
const statusAnimation = css`
${status === 'loading' && `
&::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}
`
return (
<div
className="relative p-4 bg-white rounded-lg shadow-md" // Tailwind: 基本
css={[statusAnimation, customAnimation]} // Emotion: カスタマイズ
>
{/* コンテンツ */}
</div>
)
}
最終的な推奨事項
以下の図は、プロジェクトの特性に応じた技術選択の指針を示しています:
mermaidflowchart TD
start[新規プロジェクト開始] --> team_size{チームサイズ}
team_size -->|小規模 1-3人| small_team[Emotion 中心 + Tailwind 補助]
team_size -->|中規模 4-10人| medium_team[併用アプローチ]
team_size -->|大規模 10人+| large_team[Tailwind 中心 + Emotion 補助]
small_team --> flexibility[柔軟性重視]
medium_team --> balance[バランス重視]
large_team --> consistency[一貫性重視]
flexibility --> small_benefits[・素早いプロトタイピング<br/>・細かな調整が容易<br/>・個人の自由度が高い]
balance --> medium_benefits[・段階的導入が可能<br/>・役割分担が明確<br/>・スキルレベルに応じた選択]
consistency --> large_benefits[・統一されたデザイン<br/>・新メンバーの学習コスト削減<br/>・保守性の向上]
成功の鍵となるポイント:
- 明確なガイドライン: チーム内での使い分けルールを文書化
- 段階的導入: 既存プロジェクトでは小さな変更から開始
- 継続的な最適化: パフォーマンス監視と設定調整
- チーム教育: 両技術の特性と適用場面の理解促進
Emotion と Tailwind CSS の併用は、適切な戦略と設定があれば非常に強力な開発体験を提供します。両技術の特性を理解し、プロジェクトの要件に応じて最適な組み合わせを選択することで、効率的で保守性の高いアプリケーションを構築できるでしょう。
関連リンク
公式ドキュメント
設定とツール
コミュニティリソース
関連技術
- Styled Components - 他の CSS-in-JS ライブラリ
- Stitches - 高性能 CSS-in-JS
- Mantine - React コンポーネントライブラリ
- Chakra UI - モジュラー React コンポーネントライブラリ
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来