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-components | Emotion (@emotion/styled) | Linaria | vanilla-extract |
|---|---|---|---|---|
| アプローチ | CSS-in-JS(ランタイム) | CSS-in-JS(ランタイム) | CSS-in-JS(ゼロランタイム) | CSS-in-TS(ゼロランタイム) |
| ランタイムコスト | あり | あり(やや軽量) | なし | なし |
| API | styled.div テンプレートリテラル | styled.div + css prop | styled.div テンプレートリテラル | オブジェクトスタイル |
| SSR対応 | ○ | ○ | ○ | ○ |
| TypeScript | v6で同梱 | 同梱 | 同梱 | 型安全が強み |
| 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>;
}