MENU

CSS-in-JS vs CSS Modules – スタイリング手法の比較と使い分け

目次

CSS-in-JS vs CSS Modules – スタイリング手法の比較と最適な使い分け

つまり、フロントエンド開発における スタイリングの課題

React を使ったプロジェクトで、「CSS クラス名の競合を避けたい」「コンポーネントごとにスタイルを管理したい」「パフォーマンスと保守性のバランスが取りたい」という課題に直面したことはありませんか?

今回は、CSS Modules、CSS-in-JS(Styled Components、Emotion)、Tailwind CSS という3つのスタイリング手法を比較します。そして、それぞれの具体的なメリット・デメリットとしては、そして選択基準をまとめておきたいと思います。

対象となる方

  • React でのスタイリング方法に悩んでいる方
  • 新しいプロジェクト・既存プロジェクトでのスタイリング手法の検討
  • チームのスタイリング戦略を決める開発リーダー

 ※ このドキュメントは React 18、TypeScript で書いていきます。

CSS Modules – スコープ付き CSS

CSS Modules の仕組み

CSS Modules は、CSS ファイルをモジュールとして import し、クラス名を自動で一意にスコープしてくれます。ビルド時にクラス名が変換され、競合を防ぎます。

Button.tsx と Button.module.css をセットで作成
/* Button.module.css */
.button {
  background-color: #0066cc;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.button:hover {
  background-color: #0052a3;
}

.button--primary {
  background-color: #ff6b35;
}

.button--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
// Button.tsx
import styles from './Button.module.css';

interface ButtonProps {
  variant?: 'primary' | 'default';
  disabled?: boolean;
  onClick?: () => void;
  children: React.ReactNode;
}

export function Button({ variant = 'default', disabled, onClick, children }: ButtonProps) {
  const className = [
    styles.button,
    variant === 'primary' && styles['button--primary'],
    disabled && styles['button--disabled'],
  ]
    .filter(Boolean)
    .join(' ');

  return (
    <button className={className} onClick={onClick} disabled={disabled}>
      {children}
    </button>
  );
}

CSS Modules のメリット・デメリット

メリット デメリット
スコープが自動でユニーク(クラス名競合なし) 複雑な条件付きスタイルが書きにくい
CSS の標準機能を活用(学習コスト低) 動的なスタイル値(色、サイズ)の実装が手間
パフォーマンス最適化(CSS ファイル分割可) CSS と JavaScript ファイルを分ける必要
ブラウザで標準サポート BEM などの命名規則が必須

CSS Modules の適用場面

  • 従来の CSS に習熟しているチーム
  • CSS ファイルの最適化が重要なプロジェクト
  • デザインシステムが安定している場合
  • 複雑な条件付きスタイルが少ない

CSS-in-JS:Styled Components と Emotion

CSS-in-JS の基本

CSS-in-JS は、JavaScript でスタイルを定義し、動的にスタイルを生成します。Styled Components と Emotion が代表的です。

npm install styled-components
npm install --save-dev @types/styled-components
// Button.tsx
import styled from 'styled-components';

const StyledButton = styled.button`
  background-color: #0066cc;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: #0052a3;
  }

  ${(props) =>
    props.variant === 'primary' &&
    `
    background-color: #ff6b35;
    `}

  ${(props) =>
    props.disabled &&
    `
    opacity: 0.5;
    cursor: not-allowed;
    `}
`;

interface ButtonProps {
  variant?: 'primary' | 'default';
  disabled?: boolean;
  onClick?: () => void;
  children: React.ReactNode;
}

export function Button({ variant = 'default', disabled, onClick, children }: ButtonProps) {
  return (
    <StyledButton variant={variant} disabled={disabled} onClick={onClick}>
      {children}
    </StyledButton>
  );
}

Emotion での実装

npm install @emotion/react @emotion/styled
import styled from '@emotion/styled';

const StyledButton = styled.button`
  background-color: ${(props: any) => (props.variant === 'primary' ? '#ff6b35' : '#0066cc')};
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: ${(props: any) =>
      props.variant === 'primary' ? '#ff8c42' : '#0052a3'};
  }

  ${(props: any) =>
    props.disabled &&
    `
    opacity: 0.5;
    cursor: not-allowed;
    `}
`;

export function Button({ variant = 'default', disabled, onClick, children }: ButtonProps) {
  return (
    <StyledButton variant={variant} disabled={disabled} onClick={onClick}>
      {children}
    </StyledButton>
  );
}

CSS-in-JS のメリット・デメリット

メリット デメリット
動的なスタイル値が簡単(props, 変数) Runtime CSS 生成(パフォーマンスオーバーヘッド)
JavaScript でスタイルロジック統一 バンドルサイズ増(ライブラリ込み)
スコープ自動化(クラス名競合なし) CSS-in-JS ライブラリの学習コスト
コンポーネント + スタイルを同じファイルで IDE の CSS 補完が弱い場合がある

CSS-in-JS の適用場面

  • 動的なスタイル(props に応じた色変更など)が多い
  • デザイントークン(色、サイズなど)の一元管理
  • コンポーネント + スタイルを同じファイルで管理したい
  • テーマ切り替え(ライト/ダーク モード)の実装

Tailwind CSS – ユーティリティファースト

Tailwind CSS の仕組み

Tailwind CSS は、事前に定義されたユーティリティクラスを HTML に適用するアプローチです。CSS ファイルを書かず、クラス名の組み合わせでデザインを構築します。

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// Button.tsx
interface ButtonProps {
  variant?: 'primary' | 'default';
  disabled?: boolean;
  onClick?: () => void;
  children: React.ReactNode;
}

export function Button({
  variant = 'default',
  disabled,
  onClick,
  children,
}: ButtonProps) {
  const baseClasses =
    'px-5 py-2 border-none rounded cursor-pointer text-white font-medium';
  const variantClasses =
    variant === 'primary'
      ? 'bg-orange-500 hover:bg-orange-600'
      : 'bg-blue-600 hover:bg-blue-700';
  const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : '';

  return (
    <button
      className={`${baseClasses} ${variantClasses} ${disabledClasses}`}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

clsx ライブラリで可読性向上

npm install clsx
import clsx from 'clsx';

export function Button({
  variant = 'default',
  disabled,
  onClick,
  children,
}: ButtonProps) {
  return (
    <button
      className={clsx(
        'px-5 py-2 border-none rounded cursor-pointer text-white font-medium',
        {
          'bg-orange-500 hover:bg-orange-600': variant === 'primary',
          'bg-blue-600 hover:bg-blue-700': variant === 'default',
          'opacity-50 cursor-not-allowed': disabled,
        }
      )}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

Tailwind CSS のメリット・デメリット

メリット デメリット
CSS ファイルを書かない(生産性向上) クラス名が HTML に長く記述される
デザイン一貫性(事前定義トークン) IDE の補完が必須
CSS ファイル削減(PurgeCSS 対応) 複雑なカスタマイズは別途 CSS 必要
ビルド最適化(不要クラスを削除) 学習コスト(クラス名の暗記)

Tailwind CSS の適用場面

  • 開発速度を最優先したいプロジェクト
  • デザイントークンが厳格に定義されている
  • スタートアップ・プロトタイプ
  • 複雑なカスタムスタイルが少ない

3つのアプローチの比較表

観点 CSS Modules CSS-in-JS Tailwind
学習コスト 低 中 中
開発速度 普通 速い 最速
パフォーマンス 優秀 中 優秀
スコープ管理 自動 自動 手動
動的スタイル 難 簡単 中
ファイル構成 分離 統一 HTML に集中
カスタマイズ性 高 高 中

プロジェクト規模・チーム構成別の選択基準

小規模プロジェクト(1-2 人、短期間)

推奨:Tailwind CSS

  • 開発速度が最優先
  • スタートアップ・プロトタイプに最適
  • デザイン決定が少なくて済む

中規模プロジェクト(3-10 人チーム)

推奨:CSS-in-JS(Styled Components)または Tailwind + CSS Modules

  • 複雑なコンポーネントが増えてくる
  • テーマ切り替え(ライト/ダーク モード)が必要
  • チーム内で命名規則の統一が重要

大規模プロジェクト(10+ 人チーム)

推奨:CSS Modules + デザイントークン、または デザインシステム

  • 保守性・パフォーマンスが最優先
  • デザインシステムが確立されている
  • 複数チームでの分業が必要

実装例:Design System での統一

CSS Modules + Design Tokens

// tokens.ts
export const colors = {
  primary: '#0066cc',
  secondary: '#ff6b35',
  gray: '#333333',
  lightGray: '#f5f5f5',
};

export const spacing = {
  xs: '4px',
  sm: '8px',
  md: '16px',
  lg: '24px',
};

export const typography = {
  headingLarge: '24px',
  body: '16px',
  caption: '12px',
};
/* Button.module.css */
@import './tokens.css';

.button {
  background-color: var(--color-primary);
  color: white;
  padding: var(--spacing-sm) var(--spacing-md);
  font-size: var(--typography-body);
}

ハイブリッドアプローチ:Tailwind + CSS Modules

単純なコンポーネントは Tailwind、複雑な部分は CSS Modules という混合アプローチもあります。

import styles from './Card.module.css';

export function Card({ children, variant = 'default' }: any) {
  return (
    <div className={`p-4 rounded-lg ${styles[`card--${variant}`]}`}>
      {children}
    </div>
  );
}

まとめ

以上で CSS スタイリング手法の比較を終えたいと思います。

CSS Modules、CSS-in-JS、Tailwind CSS には、それぞれ異なるアプローチと強みがあります。「どれが最高か」ではなく、「プロジェクトの要件に最適か」を基準に選択することが重要です。小規模プロジェクトなら Tailwind で速度重視、大規模チームなら CSS Modules で保守性重視、といった具合に、柔軟に判断してください。

スタイリング手法の選択は、プロジェクト成功を左右する大きな決定です。チーム全体で合意のうえ、最適なアプローチを選択していきましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次