emotion、sass、styled-components、styled-jsx は、ウェブアプリケーションのスタイリングを異なるアプローチで解決する主要なライブラリです。sass は CSS に変数やネスト機能を追加する従来のプリアプロセッサであり、ビルド時に CSS を生成します。一方、emotion と styled-components は CSS-in-JS ライブラリで、JavaScript 内でスタイルを定義し、ランタイムまたはビルド時に CSS を注入します。styled-jsx は React コンポーネント内に CSS を直接記述できるスコープ付き CSS ソリューションで、特に Next.js との相性が抜群です。これらはそれぞれ、開発者の好み、プロジェクトの規模、パフォーマンス要件に応じて選定されます。
ウェブ開発において、スタイルの管理方法はアプリケーションの保守性やパフォーマンスに直結します。emotion、sass、styled-components、styled-jsx はそれぞれ異なる哲学を持っており、どれを選ぶべきかはプロジェクトの要件によります。ここでは、実開発で直面する具体的なシナリオに基づき、これら 4 つのライブラリを技術的に比較します。
スタイルをどのように記述するかは、開発体験(DX)に最も影響します。
sass は独立した .scss ファイルでスタイルを定義します。
/* button.scss */
$primary-color: #0070f3;
.button {
background: $primary-color;
padding: 10px 20px;
&:hover {
opacity: 0.9;
}
}
emotion は JavaScript 内で css 関数または styled を使います。
css プロパティを使ってコンポーネントに適用します。// Emotion
import { css } from '@emotion/react';
const style = css({
backgroundColor: '#0070f3',
padding: '10px 20px',
'&:hover': { opacity: 0.9 }
});
function Button() {
return <button css={style}>Click</button>;
}
styled-components はタグ付きテンプレートリテラルを使用します。
// styled-components
import styled from 'styled-components';
const Button = styled.button`
background: #0070f3;
padding: 10px 20px;
&:hover { opacity: 0.9; }
`;
function App() {
return <Button>Click</Button>;
}
styled-jsx はコンポーネント内に <style jsx> タグを埋め込みます。
// styled-jsx
function Button() {
return (
<>
<button>Click</button>
<style jsx>{`
button {
background: #0070f3;
padding: 10px 20px;
}
button:hover {
opacity: 0.9;
}
`}</style>
</>
);
}
大規模プロジェクトでは、色やフォントを一元管理する必要があります。
sass はグローバル変数ファイルを読み込みます。
/* _variables.scss */
$brand-color: #0070f3;
/* main.scss */
@import 'variables';
.box { color: $brand-color; }
emotion は ThemeProvider を使って動的なテーマを提供します。
theme オブジェクトをスタイル関数から参照します。// Emotion
import { ThemeProvider, css } from '@emotion/react';
const theme = { colors: { primary: '#0070f3' } };
function ThemedBox() {
return (
<ThemeProvider theme={theme}>
<div css={t => css({ color: t.colors.primary })}>Text</div>
</ThemeProvider>
);
}
styled-components も ThemeProvider を採用しています。
props.theme を使う形です。// styled-components
import { ThemeProvider } from 'styled-components';
const theme = { colors: { primary: '#0070f3' } };
const Box = styled.div`
color: ${props => props.theme.colors.primary};
`;
function App() {
return <ThemeProvider theme={theme}><Box>Text</Box></ThemeProvider>;
}
styled-jsx はテーマ機能を提供しません。
// styled-jsx
function ThemedBox() {
return (
<>
<div className="box">Text</div>
<style jsx>{`
.box {
color: var(--brand-color);
}
`}</style>
<style global>{`
:root { --brand-color: #0070f3; }
`}</style>
</>
);
}
スタイルが意図せず他の要素に影響を与えないようにすることは重要です。
sass はデフォルトでグローバルスコープです。
/* sass (Global by default) */
.button { /* Affects all .button elements */ }
emotion は自動でユニークなクラス名を生成します。
// Emotion (Scoped)
const className = css({ color: 'red' });
// Renders class like "css-12345", scoped to this usage
styled-components も同様に自動スコーピングを行います。
// styled-components (Scoped)
const StyledButton = styled.button` color: red; `;
// Renders class like "sc-bdVaJa", scoped to component
styled-jsx はコンポーネントローカルなスコープを強制します。
<style jsx> 内のスタイルは、そのコンポーネントの JSX にのみ適用されます。:global() が必要です。// styled-jsx (Scoped)
function Component() {
return (
<div>
<style jsx>{`div { color: red; }`}</style>
</div>
);
}
サーバーサイドレンダリング(SSR)環境での挙動は、SEO や初期表示速度に影響します。
sass はビルド時に静的な CSS ファイルを出力します。
<!-- sass output -->
<link rel="stylesheet" href="/static/styles.css" />
emotion は SSR 時にスタイルを抽出して HTML に埋め込むことができます。
@emotion/server を使うことで、FOUC(スタイル未適用での点滅)を防げます。// Emotion SSR
import { extractCritical } from '@emotion/server';
// Extract styles during render and inject into <head>
styled-components も SSR サポートが充実しています。
StyleSheetManager や SSR 用プラグインを使って、スタイルを事前に収集します。// styled-components SSR
import { ServerStyleSheet } from 'styled-components';
// Collect styles during render and inject style tags
styled-jsx はビルド時に CSS を解決するハイブリッドなアプローチです。
// styled-jsx (Next.js)
// Styles are extracted to separate CSS files during build automatically
// No extra runtime configuration needed for SSR
| 特徴 | sass | emotion | styled-components | styled-jsx |
|---|---|---|---|---|
| 構文 | SCSS ファイル | JS オブジェクト/テンプレート | テンプレートリテラル | JSX 内 <style> タグ |
| スコープ | グローバル (要設定) | 自動スコープ | 自動スコープ | コンポーネントローカル |
| テーマ | 変数 (ビルド時) | ThemeProvider (ランタイム) | ThemeProvider (ランタイム) | CSS 変数 (手動) |
| SSR | 静的 CSS | 抽出可能 | 抽出可能 | 自動最適化 (Next.js) |
| 依存 | なし (CSS 出力) | ランタイムあり | ランタイムあり | ビルド時解決 |
sass は、伝統的な CSS 開発フローを維持したい場合や、ランタイムオーバーヘッドを完全に排除したい場合に最適です。既存のデザインシステムや、CSS 専門のチームがいる環境で力を発揮します。
emotion は、柔軟性と機能性のバランスを求める場合に適しています。React 以外のフレームワークとの共存や、高度な動的スタイリングが必要な複雑なアプリケーションで選ばれます。
styled-components は、React 生態系に深く統合された開発体験を提供します。コンポーネントとスタイルを一体化させ、チーム内で一貫したコーディングスタイルを強制したい場合に有効です。
styled-jsx は、Next.js を使用するプロジェクトにおけるデファクトスタンダードです。設定の手間をかけずに、パフォーマンスと開発効率の両方を得たい場合に最も合理的な選択となります。
最終的には、チームの習熟度と、アプリケーションが求めるパフォーマンス要件のバランスを見て決定することが重要です。
styled-components を選ぶべきなのは、React 中心の開発を行っており、コンポーネントとスタイルを完全に一体化させたい場合です。タグ付きテンプレートリテラルによる直感的な構文と、強力な TypeScript サポートが特徴です。スタイルがコンポーネントに密結合するため、コンポーネントの移植性が高まり、大規模な React アプリケーションで維持管理しやすい構造を作れます。
emotion を選ぶべきなのは、React 以外のフレームワークでもスタイルを共有したい場合や、css プロパティによる柔軟なスタイリングが必要な場合です。ランタイムのパフォーマンスと機能性のバランスが良く、サーバーサイドレンダリング(SSR)のサポートも充実しています。大規模なシステムで、コンポーネントのスタイルを動的に制御する必要があるプロジェクトに適しています。
sass を選ぶべきなのは、既存の CSS 資産を継承する場合や、チームが CSS 分離の原則を好む場合です。ビルド時に CSS が生成されるため、ランタイムでの JavaScript 処理が不要で、パフォーマンス面で有利です。CSS 変数やミックスインなど、成熟した機能セットを必要とする伝統的な開発スタイルに最適です。
styled-jsx を選ぶべきなのは、Next.js を使用しており、ビルド時に CSS を解決してランタイムオーバーヘッドを減らしたい場合です。コンポーネントローカルなスコープ付き CSS を標準の <style> タグで記述できるため、学習コストが低く、ブラウザのネイティブ機能に近い挙動をします。Vercel 環境や Next.js プロジェクトでは最も統合しやすい選択肢です。
styled-components is largely maintained by one person. Please help fund the project for consistent long-term support and updates: Open Collective
Style React components with real CSS, scoped automatically and delivered only when needed. No class name juggling, no separate files, no build step required.
@types install, no manual generics.npm install styled-components
pnpm add styled-components
yarn add styled-components
Vary styles based on component props. Prefix transient props with $ to keep them off the DOM.
import styled from 'styled-components';
const Button = styled.button<{ $primary?: boolean }>`
background: ${props => (props.$primary ? 'palevioletred' : 'white')};
color: ${props => (props.$primary ? 'white' : 'palevioletred')};
font-size: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
<Button>Normal</Button>
<Button $primary>Primary</Button>
Build variants on top of existing styled components.
const TomatoButton = styled(Button)`
background: tomato;
color: white;
border-color: tomato;
`;
as propSwap the rendered element without changing styles.
// Renders a <a> tag with Button styles
<Button as="a" href="/home">Link Button</Button>
Use & to reference the component's generated class name—works with pseudo-classes, pseudo-elements, and nested selectors.
const Input = styled.input`
border: 1px solid #ccc;
border-radius: 4px;
padding: 0.5em;
&:focus {
border-color: palevioletred;
outline: none;
}
&::placeholder {
color: #aaa;
}
`;
Define @keyframes once, reference them across components. Names are scoped automatically.
import styled, { keyframes } from 'styled-components';
const rotate = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
const Spinner = styled.div`
animation: ${rotate} 1s linear infinite;
width: 40px;
height: 40px;
border: 3px solid palevioletred;
border-top-color: transparent;
border-radius: 50%;
`;
Share design tokens across your app via React context. Every styled component receives props.theme.
import styled, { ThemeProvider } from 'styled-components';
const theme = {
fg: 'palevioletred',
bg: 'white',
};
const Card = styled.div`
background: ${props => props.theme.bg};
color: ${props => props.theme.fg};
padding: 2em;
`;
<ThemeProvider theme={theme}>
<Card>Themed content</Card>
</ThemeProvider>
createTheme turns your tokens into CSS custom properties. Class name hashes stay stable across theme variants—no hydration mismatch when switching light/dark.
import styled, { createTheme, ThemeProvider } from 'styled-components';
const { theme, GlobalStyle: ThemeVars } = createTheme({
colors: {
fg: 'palevioletred',
bg: 'white',
},
space: {
md: '1rem',
},
});
const Card = styled.div`
color: ${theme.colors.fg}; /* var(--sc-colors-fg, palevioletred) */
background: ${theme.colors.bg};
padding: ${theme.space.md};
`;
// Render <ThemeVars /> at the root to emit the CSS variable declarations
// Pass the theme to ThemeProvider for stable hashes
<ThemeProvider theme={theme}>
<ThemeVars />
<Card>Token-driven content</Card>
</ThemeProvider>
cssExtract reusable style blocks to share across components or apply conditionally.
import styled, { css } from 'styled-components';
const truncate = css`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
const Label = styled.span`
${truncate}
max-width: 200px;
`;
Wrap any component that accepts a className prop.
import styled from 'styled-components';
import { Link } from 'react-router-dom';
const StyledLink = styled(Link)`
color: palevioletred;
text-decoration: none;
&:hover {
text-decoration: underline;
}
`;
Inject app-wide CSS like resets and font faces. Supports theming and dynamic updates.
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
body {
margin: 0;
font-family: system-ui, sans-serif;
}
`;
// Render <GlobalStyle /> at the root of your app
Set default or static HTML attributes so consumers don't have to.
const PasswordInput = styled.input.attrs({
type: 'password',
placeholder: 'Enter password',
})`
border: 1px solid #ccc;
padding: 0.5em;
`;
Contributing guidelines | Code of Conduct | awesome-styled-components
This project exists thanks to all the people who contribute.
Thank you to all our backers! [Become a backer]
Support this project by becoming a sponsor. [Become a sponsor]
This project builds on earlier work by Charlie Somerville, Nik Graf, Sunil Pai, Michael Chan, Andrey Popp, Jed Watson, and Andrey Sitnik. Special thanks to @okonet for the logo.
Licensed under the MIT License, Copyright © 2016-present styled-components contributors. See LICENSE for details.