styled-components の使い方

CSS for the <Component> Age. Style components your way with speed, strong typing, and flexibility.

v6.3.128.9M/週MITスタイリング
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

styled-components の使い方完全ガイド

一言でいうと

styled-components は、JavaScript(TypeScript)のコード内にCSSを記述し、Reactコンポーネントとスタイルを一体化させるCSS-in-JSライブラリです。タグ付きテンプレートリテラルを活用し、コンポーネント単位でスコープされたスタイルを実現します。

どんな時に使う?

  • コンポーネント単位でスタイルを完全にカプセル化したい時 — クラス名の衝突を気にせず、コンポーネントごとに独立したスタイルを定義できます
  • propsに応じて動的にスタイルを切り替えたい時 — JavaScriptの変数やpropsをそのままCSS内で参照でき、条件分岐による動的スタイリングが容易です
  • デザインシステムやテーマを一元管理したい時ThemeProvider を使って、カラーパレットやスペーシングなどのデザイントークンをアプリ全体に配布できます

インストール

# npm
npm install styled-components

# yarn
yarn add styled-components

# pnpm
pnpm add styled-components

TypeScriptを使う場合、v6系では型定義が本体に同梱されているため、@types/styled-components の追加インストールは不要です。

注意: 本記事はstyled-components v6系(6.x)を前提としています。v5以前とはAPIや内部挙動が異なる部分があります。

基本的な使い方

最もよく使うパターンは、styled.タグ名 でスタイル付きコンポーネントを作成する方法です。

import styled from 'styled-components';

// スタイル付きボタンコンポーネントを定義
const PrimaryButton = styled.button`
  background-color: #0070f3;
  color: #ffffff;
  font-size: 16px;
  padding: 12px 24px;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 0.2s ease;

  &:hover {
    background-color: #005bb5;
  }

  &:disabled {
    background-color: #cccccc;
    cursor: not-allowed;
  }
`;

// 通常のReactコンポーネントとして使用
function App() {
  return (
    <div>
      <PrimaryButton onClick={() => alert('clicked!')}>
        送信する
      </PrimaryButton>
      <PrimaryButton disabled>無効なボタン</PrimaryButton>
    </div>
  );
}

export default App;

生成されるHTML要素には一意のクラス名が自動付与されるため、スタイルの衝突は発生しません。

よく使うAPI

1. propsによる動的スタイル

コンポーネントのpropsに応じてスタイルを動的に変更できます。

import styled from 'styled-components';

interface ButtonProps {
  $variant?: 'primary' | 'danger' | 'outline';
  $size?: 'sm' | 'md' | 'lg';
}

const Button = styled.button<ButtonProps>`
  border: 2px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;

  /* サイズの切り替え */
  padding: ${({ $size }) => {
    switch ($size) {
      case 'sm': return '6px 12px';
      case 'lg': return '16px 32px';
      default:   return '10px 20px';
    }
  }};

  font-size: ${({ $size }) => {
    switch ($size) {
      case 'sm': return '12px';
      case 'lg': return '18px';
      default:   return '14px';
    }
  }};

  /* バリアントの切り替え */
  background-color: ${({ $variant }) => {
    switch ($variant) {
      case 'danger':  return '#e53e3e';
      case 'outline': return 'transparent';
      default:        return '#0070f3';
    }
  }};

  color: ${({ $variant }) =>
    $variant === 'outline' ? '#0070f3' : '#ffffff'
  };

  border-color: ${({ $variant }) =>
    $variant === 'outline' ? '#0070f3' : 'transparent'
  };
`;

// 使用例
function App() {
  return (
    <>
      <Button $variant="primary" $size="md">Primary</Button>
      <Button $variant="danger" $size="lg">Delete</Button>
      <Button $variant="outline" $size="sm">Cancel</Button>
    </>
  );
}

Tips: v6ではprops名の先頭に $ を付ける「Transient Props」が推奨されています。$ 付きのpropsはDOMに渡されないため、HTMLの不正な属性警告を防げます。

2. ThemeProvider によるテーマ管理

アプリ全体で共通のデザイントークンを管理できます。

import styled, { ThemeProvider, DefaultTheme } from 'styled-components';

// テーマの型定義
declare module 'styled-components' {
  export interface DefaultTheme {
    colors: {
      primary: string;
      secondary: string;
      text: string;
      background: string;
    };
    spacing: {
      sm: string;
      md: string;
      lg: string;
    };
  }
}

const lightTheme: DefaultTheme = {
  colors: {
    primary: '#0070f3',
    secondary: '#7928ca',
    text: '#1a1a1a',
    background: '#ffffff',
  },
  spacing: {
    sm: '8px',
    md: '16px',
    lg: '32px',
  },
};

const darkTheme: DefaultTheme = {
  colors: {
    primary: '#3291ff',
    secondary: '#a855f7',
    text: '#ededed',
    background: '#1a1a1a',
  },
  spacing: {
    sm: '8px',
    md: '16px',
    lg: '32px',
  },
};

// テーマを参照するコンポーネント
const Card = styled.div`
  background-color: ${({ theme }) => theme.colors.background};
  color: ${({ theme }) => theme.colors.text};
  padding: ${({ theme }) => theme.spacing.lg};
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
`;

const Title = styled.h2`
  color: ${({ theme }) => theme.colors.primary};
  margin-bottom: ${({ theme }) => theme.spacing.md};
`;

// アプリのルートでThemeProviderを配置
function App() {
  const isDarkMode = false; // 実際にはstateで管理

  return (
    <ThemeProvider theme={isDarkMode ? darkTheme : lightTheme}>
      <Card>
        <Title>テーマ対応カード</Title>
        <p>ThemeProviderでテーマを一元管理しています。</p>
      </Card>
    </ThemeProvider>
  );
}

3. 既存コンポーネントの拡張(styled() による継承)

既存のスタイル付きコンポーネントを拡張して、新しいコンポーネントを作成できます。

import styled from 'styled-components';

const BaseButton = styled.button`
  padding: 10px 20px;
  font-size: 14px;
  font-weight: 600;
  border: none;
  border-radius: 6px;
  cursor: pointer;
`;

// BaseButtonを拡張
const SuccessButton = styled(BaseButton)`
  background-color: #38a169;
  color: #ffffff;

  &:hover {
    background-color: #2f855a;
  }
`;

const GhostButton = styled(BaseButton)`
  background-color: transparent;
  color: #718096;
  border: 1px solid #e2e8f0;

  &:hover {
    background-color: #f7fafc;
  }
`;

// サードパーティのコンポーネントにも適用可能
// ※ 対象コンポーネントが className を受け取る必要があります
import { Link } from 'react-router-dom';

const StyledLink = styled(Link)`
  color: #0070f3;
  text-decoration: none;
  font-weight: 500;

  &:hover {
    text-decoration: underline;
  }
`;

4. createGlobalStyle によるグローバルスタイル

リセットCSSやbodyのスタイルなど、グローバルに適用したいスタイルを定義できます。

import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }

  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
      'Helvetica Neue', Arial, sans-serif;
    background-color: ${({ theme }) => theme.colors.background};
    color: ${({ theme }) => theme.colors.text};
    line-height: 1.6;
  }

  a {
    color: inherit;
    text-decoration: none;
  }
`;

// アプリのルートに配置
function App() {
  return (
    <ThemeProvider theme={lightTheme}>
      <GlobalStyle />
      <main>{/* アプリ本体 */}</main>
    </ThemeProvider>
  );
}

5. css ヘルパーとアニメーション(keyframes)

スタイルの再利用や、CSSアニメーションの定義に使います。

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

// 再利用可能なスタイルブロック
const truncate = css`
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
`;

const flexCenter = css`
  display: flex;
  align-items: center;
  justify-content: center;
`;

const Label = styled.span`
  ${truncate}
  max-width: 200px;
  display: inline-block;
`;

const CenteredBox = styled.div`
  ${flexCenter}
  height: 100vh;
`;

// keyframesによるアニメーション
const fadeIn = keyframes`
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
`;

const spin = keyframes`
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
`;

const FadeInCard = styled.div`
  animation: ${fadeIn} 0.3s ease-out;
  padding: 24px;
  border-radius: 8px;
  background: #ffffff;
`;

const Spinner = styled.div`
  width: 32px;
  height: 32px;
  border: 3px solid #e2e8f0;
  border-top-color: #0070f3;
  border-radius: 50%;
  animation: ${spin} 0.8s linear infinite;
`;

類似パッケージとの比較

特徴styled-componentsEmotion (@emotion/styled)Linariavanilla-extract
アプローチCSS-in-JS(ランタイム)CSS-in-JS(ランタイム)CSS-in-JS(ゼロランタイム)CSS-in-TS(ゼロランタイム)
ランタイムコストありあり(やや軽量)なしなし
APIstyled.div テンプレートリテラルstyled.div + css propstyled.div テンプレートリテラルオブジェクトスタイル
SSR対応
TypeScriptv6で同梱同梱同梱型安全が強み
React Server Components制限あり制限あり対応対応
npm週間DL数非常に多い非常に多い少なめ増加中
学習コスト低い低い中程度中程度

選定の目安:

  • ランタイムCSS-in-JSで実績重視 → styled-components or Emotion
  • バンドルサイズ・パフォーマンス最優先 → vanilla-extract or Linaria
  • Next.js App Router(RSC)を本格活用 → ゼロランタイム系を検討

注意点・Tips

1. Transient Props($ プレフィックス)を使う

スタイリング専用のpropsには必ず $ を付けましょう。付けないとDOMに不正な属性が渡され、Reactの警告が出ます。

// ❌ BAD: isActive がDOMに渡される
const Box = styled.div<{ isActive: boolean }>`
  color: ${({ isActive }) => (isActive ? 'red' : 'gray')};
`;

// ✅ GOOD: $isActive はDOMに渡されない
const Box = styled.div<{ $isActive: boolean }>`
  color: ${({ $isActive }) => ($isActive ? 'red' : 'gray')};
`;

2. レンダー関数内でstyled定義しない

コンポーネントの中で styled.div を呼ぶと、レンダーのたびに新しいコンポーネントが生成され、パフォーマンスが著しく劣化します。

// ❌ BAD: レンダーごとに再生成される
function App() {
  const Wrapper = styled.div`
    padding: 16px;
  `;
  return <Wrapper>...</Wrapper>;
}

比較記事