Emotion の composition(合成)を使いこなすコツ

React 開発において、スタイリングは避けて通れない重要な要素です。特に大規模なプロジェクトでは、一貫性のあるデザインを保ちながら、柔軟性も求められます。
Emotion の composition(合成)機能は、まさにこの課題を解決する魔法のような機能です。複数のスタイルを組み合わせることで、再利用可能で保守性の高いコンポーネントを作成できます。
この記事では、Emotion の composition の基本から実践的な活用方法まで、実際のコード例とエラーケースを交えて詳しく解説します。読み終えた頃には、あなたも Emotion の composition を使いこなせるようになっているはずです。
背景
CSS-in-JS と Emotion の位置づけ
CSS-in-JS は、JavaScript 内で CSS を記述する手法です。従来の CSS ファイルとは異なり、コンポーネントとスタイルを密接に結びつけることができます。
Emotion は、この CSS-in-JS ライブラリの中でも特に人気が高く、直感的な API と優れたパフォーマンスを提供します。
javascript// 従来の CSS ファイル
.button {
background: blue;
color: white;
padding: 10px 20px;
}
// Emotion での記述
const Button = styled.button`
background: blue;
color: white;
padding: 10px 20px;
`;
Emotion の最大の特徴は、この styled コンポーネント同士を組み合わせる composition 機能にあります。これにより、小さなスタイルの部品から複雑な UI を構築できるようになります。
課題
従来の CSS クラス管理の限界と composition が必要な理由
従来の CSS クラス管理では、以下のような課題に直面します:
クラス名の衝突
css/* 複数の開発者が同じクラス名を使用してしまう */
.button {
background: red;
}
/* 別のファイルで同じクラス名 */
.button {
background: blue; /* 上書きされてしまう */
}
スタイルの依存関係が不明確
css/* どのクラスがどのクラスに依存しているか分からない */
.primary-button {
/* 基本ボタンスタイル */
}
.primary-button:hover {
/* ホバー時のスタイル */
}
/* この依存関係が複雑になると管理が困難 */
再利用性の低さ
css/* 似たようなスタイルを何度も書く必要がある */
.small-button {
padding: 5px 10px;
font-size: 12px;
}
.medium-button {
padding: 10px 20px;
font-size: 14px;
}
.large-button {
padding: 15px 30px;
font-size: 16px;
}
これらの課題を解決するのが、Emotion の composition 機能です。スタイルを部品化し、組み合わせることで、保守性と再利用性を大幅に向上させることができます。
解決策
composition の基本構文
Emotion の composition は、css
関数と styled
コンポーネントを組み合わせることで実現します。
javascriptimport styled, { css } from '@emotion/styled';
// 基本スタイルを定義
const baseButtonStyles = css`
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
`;
// サイズバリエーション
const smallButton = css`
padding: 8px 16px;
font-size: 14px;
`;
const largeButton = css`
padding: 16px 32px;
font-size: 18px;
`;
// 色バリエーション
const primaryButton = css`
background: #007bff;
color: white;
&:hover {
background: #0056b3;
}
`;
const secondaryButton = css`
background: #6c757d;
color: white;
&:hover {
background: #545b62;
}
`;
複数のスタイルを組み合わせる方法
定義したスタイルを組み合わせて、実際のコンポーネントを作成します。
javascript// 基本ボタンコンポーネント
const Button = styled.button`
${baseButtonStyles}
`;
// サイズと色を組み合わせたバリエーション
const SmallPrimaryButton = styled.button`
${baseButtonStyles}
${smallButton}
${primaryButton}
`;
const LargeSecondaryButton = styled.button`
${baseButtonStyles}
${largeButton}
${secondaryButton}
`;
条件付き composition の実装
props を使って動的にスタイルを組み合わせることも可能です。
javascript// 条件付きスタイルの定義
const getSizeStyles = (size) => {
switch (size) {
case 'small':
return smallButton;
case 'large':
return largeButton;
default:
return css`
padding: 12px 24px;
font-size: 16px;
`;
}
};
const getVariantStyles = (variant) => {
switch (variant) {
case 'primary':
return primaryButton;
case 'secondary':
return secondaryButton;
default:
return css`
background: #e9ecef;
color: #495057;
&:hover {
background: #dee2e6;
}
`;
}
};
// 動的な composition
const DynamicButton = styled.button`
${baseButtonStyles}
${(props) => getSizeStyles(props.size)}
${(props) => getVariantStyles(props.variant)}
`;
具体例
ボタンコンポーネントでの活用
実際のプロジェクトでよく使われるボタンコンポーネントの例を見てみましょう。
javascript// ボタンの基本スタイル
const buttonBase = css`
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 6px;
cursor: pointer;
font-family: inherit;
font-weight: 500;
text-decoration: none;
transition: all 0.2s ease;
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`;
// サイズバリエーション
const buttonSizes = {
xs: css`
padding: 4px 8px;
font-size: 12px;
min-height: 24px;
`,
sm: css`
padding: 6px 12px;
font-size: 14px;
min-height: 32px;
`,
md: css`
padding: 8px 16px;
font-size: 16px;
min-height: 40px;
`,
lg: css`
padding: 12px 24px;
font-size: 18px;
min-height: 48px;
`,
};
javascript// 色バリエーション
const buttonVariants = {
primary: css`
background: #3b82f6;
color: white;
&:hover:not(:disabled) {
background: #2563eb;
}
&:active:not(:disabled) {
background: #1d4ed8;
}
`,
secondary: css`
background: #6b7280;
color: white;
&:hover:not(:disabled) {
background: #4b5563;
}
&:active:not(:disabled) {
background: #374151;
}
`,
outline: css`
background: transparent;
color: #3b82f6;
border: 2px solid #3b82f6;
&:hover:not(:disabled) {
background: #3b82f6;
color: white;
}
`,
};
javascript// 完成したボタンコンポーネント
const Button = styled.button`
${buttonBase}
${(props) => buttonSizes[props.size] || buttonSizes.md}
${(props) =>
buttonVariants[props.variant] || buttonVariants.primary}
`;
// 使用例
const App = () => (
<div>
<Button size='sm' variant='primary'>
小さいボタン
</Button>
<Button size='md' variant='secondary'>
普通のボタン
</Button>
<Button size='lg' variant='outline'>
大きいボタン
</Button>
</div>
);
カードコンポーネントでの活用
カードコンポーネントでも composition を活用できます。
javascript// カードの基本スタイル
const cardBase = css`
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: box-shadow 0.2s ease;
`;
// カードのサイズバリエーション
const cardSizes = {
small: css`
padding: 16px;
max-width: 300px;
`,
medium: css`
padding: 24px;
max-width: 500px;
`,
large: css`
padding: 32px;
max-width: 800px;
`,
};
javascript// カードのバリエーション
const cardVariants = {
default: css`
border: 1px solid #e5e7eb;
`,
elevated: css`
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
&:hover {
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
`,
outlined: css`
border: 2px solid #3b82f6;
box-shadow: none;
`,
};
// カードコンポーネント
const Card = styled.div`
${cardBase}
${(props) => cardSizes[props.size] || cardSizes.medium}
${(props) =>
cardVariants[props.variant] || cardVariants.default}
`;
フォーム要素での活用
フォーム要素でも composition を活用して、一貫性のあるデザインを実現できます。
javascript// フォーム要素の基本スタイル
const formBase = css`
border: 1px solid #d1d5db;
border-radius: 6px;
padding: 8px 12px;
font-size: 16px;
transition: border-color 0.2s ease;
&:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
&:disabled {
background: #f9fafb;
cursor: not-allowed;
}
`;
// エラー状態のスタイル
const errorState = css`
border-color: #ef4444;
&:focus {
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
`;
javascript// 入力フィールドコンポーネント
const Input = styled.input`
${formBase}
${(props) => props.hasError && errorState}
`;
// テキストエリアコンポーネント
const TextArea = styled.textarea`
${formBase}
${(props) => props.hasError && errorState}
resize: vertical;
min-height: 100px;
`;
// セレクトボックスコンポーネント
const Select = styled.select`
${formBase}
${(props) => props.hasError && errorState}
background: white;
`;
よくあるエラーと解決方法
エラー 1: css 関数のインポート忘れ
javascript// ❌ エラーが発生するコード
import styled from '@emotion/styled';
const baseStyles = css`
color: red;
`;
// エラーメッセージ:
// ReferenceError: css is not defined
javascript// ✅ 正しいコード
import styled, { css } from '@emotion/styled';
const baseStyles = css`
color: red;
`;
エラー 2: スタイルの適用順序の問題
javascript// ❌ 意図しない結果になるコード
const Button = styled.button`
${primaryButton}
${baseButtonStyles} // 後に書いたスタイルが優先される
`;
// 結果: baseButtonStyles の border-radius が primaryButton の値を上書き
javascript// ✅ 正しいコード
const Button = styled.button`
${baseButtonStyles}
${primaryButton} // 基本スタイルを先に、詳細スタイルを後に
`;
エラー 3: 条件付きスタイルでの未定義値
javascript// ❌ エラーが発生する可能性があるコード
const Button = styled.button`
${(props) =>
buttonSizes[
props.size
]}// props.size が undefined の場合エラー
`;
javascript// ✅ 安全なコード
const Button = styled.button`
${(props) => buttonSizes[props.size] || buttonSizes.md}
`;
まとめ
Emotion の composition を使いこなすためのポイントをまとめます。
基本原則
-
小さな部品から組み立てる
- 基本スタイル、サイズ、色などを分離して定義
- 再利用可能な小さなスタイルブロックを作成
-
命名規則を統一する
- スタイル変数には説明的な名前を付ける
- チーム内で一貫した命名規則を決める
-
条件付きスタイルは安全に
- デフォルト値を必ず設定する
- 未定義値に対する処理を忘れない
実践的なコツ
-
テーマとの組み合わせ
javascript
const themedButton = css` ${baseButtonStyles} background: ${(props) => props.theme.colors.primary}; color: ${(props) => props.theme.colors.white}; `;
-
メディアクエリとの組み合わせ
javascript
const responsiveButton = css` ${baseButtonStyles} @media (max-width: 768px) { padding: 8px 16px; font-size: 14px; } `;
-
アニメーションとの組み合わせ
javascript
const animatedButton = css` ${baseButtonStyles} animation: ${fadeIn} 0.3s ease-in; `;
Emotion の composition をマスターすることで、保守性が高く、再利用可能なコンポーネントライブラリを構築できます。小さなスタイルの部品を組み合わせることで、大きな UI システムを作り上げる楽しさを実感してください。
最初は複雑に感じるかもしれませんが、実際に使ってみると、その柔軟性と強力さに驚くはずです。一歩ずつ進めていけば、必ず使いこなせるようになります。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来