T-CREATOR

styled-components と Emotion の違いを徹底比較

styled-components と Emotion の違いを徹底比較

React 開発において、コンポーネントのスタイリングは避けて通れない重要な要素です。従来の CSS ファイルでのスタイリングから進化し、現在多くの開発者が CSS-in-JS ライブラリを採用しています。

特にstyled-componentsEmotionは、React エコシステムにおける二大巨頭として君臨し、多くのプロジェクトで採用されています。しかし、どちらを選ぶべきか迷われている方も多いのではないでしょうか。

この記事では、両者の違いを実際のコード例やエラー事例を交えながら徹底的に比較し、あなたのプロジェクトに最適な選択肢を見つけるお手伝いをいたします。

背景 - CSS-in-JS ライブラリが注目される理由

従来のスタイリング手法の課題

Web 開発の歴史を振り返ると、スタイリングは常に開発者を悩ませる課題でした。従来の CSS ファイルによるスタイリングでは、以下のような問題が頻繁に発生していました。

#課題具体的な問題
1グローバルスコープクラス名の衝突による予期しないスタイル適用
2デッドコード使われなくなった CSS の特定困難
3動的スタイリングProps に基づくスタイル変更の複雑さ
4メンテナンス性コンポーネントとスタイルの分離による保守困難

CSS-in-JS が解決する本質的価値

CSS-in-JS ライブラリは、これらの課題を根本から解決します。最も重要なのは、スタイルとロジックの結合による開発体験の劇的な向上です。

typescript// 従来のCSS + Reactの課題例
import './Button.css'; // 別ファイル管理が必要

const Button = ({ variant, disabled }) => {
  // 複雑な条件分岐でクラス名を管理
  const className = `button ${
    variant === 'primary'
      ? 'button--primary'
      : 'button--secondary'
  } ${disabled ? 'button--disabled' : ''}`;

  return <button className={className}>Click me</button>;
};

この従来手法では、スタイルとコンポーネントが分離されているため、変更時に複数ファイルを編集する必要があり、開発効率が下がってしまいます。

styled-components と Emotion の基本概要

styled-components - 老舗の安定感

styled-components は 2016 年にリリースされた、CSS-in-JS ライブラリの先駆者的存在です。その最大の特徴は、テンプレートリテラルを使った直感的な記法にあります。

typescriptimport styled from 'styled-components';

const Button = styled.button`
  background-color: ${(props) =>
    props.primary ? '#007bff' : '#6c757d'};
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    opacity: 0.8;
  }
`;

この記法の美しさは、CSS そのものの構文を保ちながら、JavaScript の動的な値を自然に組み込める点です。まるで従来の CSS を書いているような感覚で、Props ベースの動的スタイリングが実現できます。

Emotion - 革新的なパフォーマンス志向

Emotion は 2017 年にリリースされ、styled-components の良さを継承しつつ、パフォーマンスとバンドルサイズの最適化に重点を置いて開発されました。

typescriptimport styled from '@emotion/styled';
import { css } from '@emotion/react';

// styled記法
const Button = styled.button`
  background-color: ${(props) =>
    props.primary ? '#007bff' : '#6c757d'};
  color: white;
`;

// css記法
const buttonStyle = css`
  background-color: #007bff;
  color: white;
`;

Emotion の革新的な点は、複数の記法を提供している柔軟性です。プロジェクトの要件や開発者の好みに応じて、最適なアプローチを選択できます。

記法・書き方の違い

テンプレートリテラル記法の比較

両ライブラリとも基本的なテンプレートリテラル記法は非常に似ていますが、細かな違いが存在します。

styled-components の記法例:

typescriptimport styled, { css } from 'styled-components';

const Container = styled.div`
  padding: 20px;

  ${(props) =>
    props.centered &&
    css`
      display: flex;
      justify-content: center;
      align-items: center;
    `}
`;

// 使用例
<Container centered>
  <p>中央配置されたコンテンツ</p>
</Container>;

Emotion の記法例:

typescriptimport styled from '@emotion/styled';
import { css } from '@emotion/react';

const Container = styled.div`
  padding: 20px;

  ${(props) =>
    props.centered &&
    css`
      display: flex;
      justify-content: center;
      align-items: center;
    `}
`;

// 追加:css記法も利用可能
const centerStyle = css`
  display: flex;
  justify-content: center;
  align-items: center;
`;

css 記法での違いと活用場面

Emotion の大きな特徴の一つが、css記法の柔軟な活用です。この記法は、スタイルオブジェクトを変数として管理したい場合に威力を発揮します。

typescriptimport { css } from '@emotion/react';

// スタイルを変数として定義
const baseButtonStyle = css`
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;
`;

const primaryButtonStyle = css`
  ${baseButtonStyle}
  background-color: #007bff;
  color: white;

  &:hover {
    background-color: #0056b3;
  }
`;

// JSX内で直接使用
const MyComponent = () => (
  <button css={primaryButtonStyle}>
    クリックしてください
  </button>
);

このcss記法は、スタイルの再利用性を高め、より宣言的なコンポーネント設計を可能にします。

実際に発生するエラーと対処法

開発中によく遭遇するエラーをご紹介します。これらのエラーメッセージで検索される方も多いでしょう。

styled-components でよくあるエラー:

bash# エラーメッセージ
TypeError: Cannot read property 'attrs' of undefined

# 原因:importの間違い
import styled from 'styled-components/macro'; // ❌ 間違い
import styled from 'styled-components';       // ✅ 正しい

Emotion でよくあるエラー:

bash# エラーメッセージ
Module parse failed: Unexpected token
You may need an appropriate loader to handle this file type

# 原因:@emotion/babel-plugin の設定不足
# babel.config.js に以下を追加
{
  "plugins": ["@emotion/babel-plugin"]
}

パフォーマンス比較

ランタイムパフォーマンス

パフォーマンスは現代の Web 開発において最も重要な指標の一つです。両ライブラリのランタイムパフォーマンスを詳しく見てみましょう。

#項目styled-componentsEmotion
1初回レンダリング中程度高速
2再レンダリング中程度高速
3CSS 生成速度中程度高速
4メモリ使用量やや多め効率的

実際のベンチマークテストコード:

typescript// パフォーマンステスト用のコンポーネント
import { performance } from 'perf_hooks';
import styled from 'styled-components';
// import styled from '@emotion/styled'; // 比較用

const TestButton = styled.button`
  background: ${(props) => props.color || 'blue'};
  padding: 10px;
  margin: 5px;
`;

// レンダリング時間計測
const measureRenderTime = () => {
  const start = performance.now();

  // 1000個のボタンをレンダリング
  const buttons = Array.from({ length: 1000 }, (_, i) => (
    <TestButton
      key={i}
      color={`hsl(${i * 0.36}, 70%, 50%)`}
    >
      Button {i}
    </TestButton>
  ));

  const end = performance.now();
  console.log(`レンダリング時間: ${end - start}ms`);
};

ビルドタイムでの最適化

Emotion は、ビルドタイムでの最適化により、より効率的な CSS 生成を実現しています。

typescript// Emotion のビルドタイム最適化例
import { css } from '@emotion/react';

// 開発時のコード
const dynamicStyle = css`
  color: ${(props) => props.theme.primary};
  font-size: ${(props) => props.size}px;
`;

// ビルド後に最適化される
// → 静的な部分は事前にコンパイルされ、動的な部分のみランタイムで処理

バンドルサイズとインストール方法

バンドルサイズの比較

アプリケーションの読み込み速度に直結するバンドルサイズは、ライブラリ選択の重要な判断材料です。

#ライブラリミニファイド版Gzip 圧縮後
1styled-components76.8KB19.8KB
2@emotion/styled + @emotion/react42.6KB12.4KB
3@emotion/styled のみ28.1KB8.9KB

Emotion の方が明らかに軽量で、特に@emotion​/​styledのみを使用する場合は、大幅にバンドルサイズを削減できます。

インストール方法と初期設定

styled-components のインストール:

bash# 基本インストール
yarn add styled-components
yarn add --dev @types/styled-components

# TypeScript用の設定
# styled-components.d.ts
import 'styled-components';

declare module 'styled-components' {
  export interface DefaultTheme {
    colors: {
      primary: string;
      secondary: string;
    };
  }
}

Emotion のインストール:

bash# 基本パッケージ
yarn add @emotion/react @emotion/styled

# Babel設定(推奨)
yarn add --dev @emotion/babel-plugin

# Next.js用の設定
yarn add @emotion/server

Next.js での設定差分

Next.js 環境での設定方法も、両者で微妙に異なります。

styled-components + Next.js:

typescript// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  compiler: {
    styledComponents: true,
  },
};

module.exports = nextConfig;

Emotion + Next.js:

typescript// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 特別な設定は不要(babel-pluginで対応)
};

// babel.config.js
module.exports = {
  presets: ['next/babel'],
  plugins: ['@emotion/babel-plugin'],
};

TypeScript 対応状況

型安全性の比較

TypeScript での開発体験は、モダンな React 開発において欠かせない要素です。両ライブラリの型対応を詳しく見てみましょう。

styled-components の TypeScript 対応:

typescriptimport styled from 'styled-components';

// Propsの型定義
interface ButtonProps {
  variant: 'primary' | 'secondary';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
}

const Button = styled.button<ButtonProps>`
  background-color: ${({ variant }) =>
    variant === 'primary' ? '#007bff' : '#6c757d'
  };

  padding: ${({ size = 'medium' }) => {
    switch (size) {
      case 'small': return '8px 16px';
      case 'large': return '16px 32px';
      default: return '12px 24px';
    }
  }};

  opacity: ${({ disabled }) => disabled ? 0.6 : 1};
  cursor: ${({ disabled }) => disabled ? 'not-allowed' : 'pointer'};
`;

// 使用時に型チェックが働く
<Button variant="primary" size="large" /> // ✅ OK
<Button variant="danger" />               // ❌ 型エラー

Emotion の TypeScript 対応:

typescriptimport styled from '@emotion/styled';
import { css, SerializedStyles } from '@emotion/react';

interface ButtonProps {
  variant: 'primary' | 'secondary';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
}

const Button = styled.button<ButtonProps>`
  background-color: ${({ variant }) =>
    variant === 'primary' ? '#007bff' : '#6c757d'};
`;

// css記法での型安全性
const getButtonSize = (
  size: ButtonProps['size']
): SerializedStyles => css`
  padding: ${size === 'small'
    ? '8px 16px'
    : size === 'large'
    ? '16px 32px'
    : '12px 24px'};
`;

よくある型エラーと解決法

開発中に遭遇する型エラーの事例をご紹介します。

typescript// よくあるエラー:テーマの型が推論されない
const Button = styled.button`
  color: ${(props) =>
    props.theme.colors.primary}; // ❌ 型エラー
`;

// 解決法:DefaultTheme を拡張
declare module 'styled-components' {
  export interface DefaultTheme {
    colors: {
      primary: string;
      secondary: string;
    };
  }
}

// または、型アサーション
const Button = styled.button`
  color: ${(props: { theme: DefaultTheme }) =>
    props.theme.colors.primary};
`;

コミュニティとエコシステム

GitHub 統計とメンテナンス状況

両ライブラリのコミュニティ活動を数値で比較してみましょう。

#指標styled-componentsEmotion
1GitHub スター数40,000+17,000+
2週間ダウンロード数4,500,0005,200,000
3コントリビューター数300+200+
4最終リリース継続的継続的

興味深いことに、styled-components の方がスター数は多いものの、Emotion の方がダウンロード数では上回っています。これは、近年のパフォーマンス重視の傾向を反映しているかもしれません。

サードパーティライブラリとの統合

styled-components のエコシステム:

typescript// Polished - styled-components用のユーティリティ
import { darken, lighten } from 'polished';
import styled from 'styled-components';

const Button = styled.button`
  background-color: #007bff;

  &:hover {
    background-color: ${darken(0.1, '#007bff')};
  }

  &:active {
    background-color: ${darken(0.2, '#007bff')};
  }
`;

Emotion のエコシステム:

typescript// @emotion/css - ユーティリティ関数群
import { css, keyframes } from '@emotion/react';

const fadeIn = keyframes`
  from { opacity: 0; }
  to { opacity: 1; }
`;

const animatedStyle = css`
  animation: ${fadeIn} 0.3s ease-in-out;
`;

具体的な実装例での比較

実際のプロダクトレベルの比較

実際のアプリケーション開発でよく使われるパターンを両ライブラリで実装して比較してみましょう。

テーマを活用したダークモード対応コンポーネント:

typescript// styled-components版
import styled, {
  ThemeProvider,
  createGlobalStyle,
} from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    background-color: ${(props) =>
      props.theme.backgroundColor};
    color: ${(props) => props.theme.textColor};
    transition: all 0.3s ease;
  }
`;

const Card = styled.div`
  background-color: ${(props) =>
    props.theme.cardBackground};
  border: 1px solid ${(props) => props.theme.borderColor};
  border-radius: 8px;
  padding: 24px;
  box-shadow: ${(props) => props.theme.elevation.low};

  &:hover {
    box-shadow: ${(props) => props.theme.elevation.medium};
  }
`;

// テーマ定義
const lightTheme = {
  backgroundColor: '#ffffff',
  textColor: '#333333',
  cardBackground: '#f8f9fa',
  borderColor: '#dee2e6',
  elevation: {
    low: '0 1px 3px rgba(0,0,0,0.12)',
    medium: '0 4px 6px rgba(0,0,0,0.16)',
  },
};

const darkTheme = {
  backgroundColor: '#1a1a1a',
  textColor: '#ffffff',
  cardBackground: '#2d2d2d',
  borderColor: '#404040',
  elevation: {
    low: '0 1px 3px rgba(255,255,255,0.12)',
    medium: '0 4px 6px rgba(255,255,255,0.16)',
  },
};
typescript// Emotion版
import { css, Global, ThemeProvider } from '@emotion/react';
import styled from '@emotion/styled';

const globalStyles = (theme) => css`
  body {
    background-color: ${theme.backgroundColor};
    color: ${theme.textColor};
    transition: all 0.3s ease;
  }
`;

const Card = styled.div`
  background-color: ${(props) =>
    props.theme.cardBackground};
  border: 1px solid ${(props) => props.theme.borderColor};
  border-radius: 8px;
  padding: 24px;
  box-shadow: ${(props) => props.theme.elevation.low};

  &:hover {
    box-shadow: ${(props) => props.theme.elevation.medium};
  }
`;

// アプリケーションルート
const App = () => {
  const [isDark, setIsDark] = useState(false);
  const theme = isDark ? darkTheme : lightTheme;

  return (
    <ThemeProvider theme={theme}>
      <Global styles={globalStyles} />
      <Card>
        <h2>テーマ対応カード</h2>
        <button onClick={() => setIsDark(!isDark)}>
          {isDark ? 'ライトモード' : 'ダークモード'}
          に切り替え
        </button>
      </Card>
    </ThemeProvider>
  );
};

アニメーション実装の比較

動的な UI に欠かせないアニメーション実装も比較してみましょう。

styled-components でのアニメーション:

typescriptimport styled, { keyframes, css } from 'styled-components';

const slideIn = keyframes`
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
`;

const pulse = keyframes`
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
`;

const AnimatedButton = styled.button`
  animation: ${slideIn} 0.5s ease-out;

  ${(props) =>
    props.loading &&
    css`
      animation: ${pulse} 1s infinite;
      pointer-events: none;
    `}

  &:hover {
    transform: translateY(-2px);
    transition: transform 0.2s ease;
  }
`;

Emotion でのアニメーション:

typescriptimport { css, keyframes } from '@emotion/react';
import styled from '@emotion/styled';

const slideIn = keyframes`
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
`;

const loadingAnimation = css`
  animation: ${keyframes`
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.05); }
  `} 1s infinite;
  pointer-events: none;
`;

const AnimatedButton = styled.button`
  animation: ${slideIn} 0.5s ease-out;

  ${(props) => props.loading && loadingAnimation}

  &:hover {
    transform: translateY(-2px);
    transition: transform 0.2s ease;
  }
`;

パフォーマンス計測の実装例

実際のアプリケーションでパフォーマンスを計測するコードもご紹介します。

typescriptimport { Profiler } from 'react';

const PerformanceTest = () => {
  const onRenderCallback = (id, phase, actualDuration) => {
    console.log(`${id} ${phase}: ${actualDuration}ms`);
  };

  return (
    <Profiler
      id='StyledComponents'
      onRender={onRenderCallback}
    >
      {/* 大量のスタイル付きコンポーネント */}
      {Array.from({ length: 1000 }, (_, i) => (
        <StyledButton
          key={i}
          variant={i % 2 === 0 ? 'primary' : 'secondary'}
        >
          Button {i}
        </StyledButton>
      ))}
    </Profiler>
  );
};

まとめ - どちらを選ぶべきか

この記事を通じて、styled-components と Emotion の詳細な比較をしてまいりました。最終的にどちらを選ぶべきかは、あなたのプロジェクトの特性と価値観によって決まります。

styled-components を選ぶべき場面

以下の条件に当てはまる場合は、styled-components をお勧めします:

#条件理由
1チーム開発が主体豊富なドキュメントと学習リソース
2安定性を重視長期間の実績とコミュニティサポート
3CSS-in-JS 初心者直感的な記法で学習コストが低い
4既存プロジェクトでの採用エコシステムの充実と移行コスト

Emotion を選ぶべき場面

一方、以下の場合は Emotion の方が適しているでしょう:

#条件理由
1パフォーマンス重視軽量なバンドルサイズと高速なランタイム
2柔軟な記法が必要css 記法と styled 記法の使い分け
3モダンな開発環境最新の最適化技術とツールチェーン
4新規プロジェクト制約なく最適な選択が可能

心に留めておいていただきたいこと

技術選択において最も大切なのは、チーム全体の生産性と開発体験の向上です。ライブラリの性能差よりも、チームメンバーが快適に開発できる環境を構築することが、長期的なプロジェクトの成功につながります。

どちらを選んだとしても、以下の原則を忘れずにいてください:

  • 一貫性のあるコーディングスタイルの確立
  • 適切なコンポーネント設計による保守性の向上
  • パフォーマンス計測による継続的な改善
  • チーム全体でのナレッジ共有

あなたのプロジェクトが、ユーザーにとって価値のあるプロダクトとして成長していくことを心から願っています。styled-components と Emotion は、どちらもその目標達成のための強力なツールになってくれるはずです。

関連リンク

公式ドキュメント

TypeScript 関連

パフォーマンス・ベンチマーク

エコシステム

コミュニティ・学習リソース