emotion 和 styled-components 是 React 生态中最主流的 CSS-in-JS 底层引擎,负责将样式逻辑直接写入 JavaScript 组件中,提供样式隔离和动态样式能力。theme-ui 是基于 emotion 构建的高层设计系统库,通过约束性的 Theme 对象和 sx 属性简化组件样式开发。rebass 是早期的无头组件库,基于 styled-system,但目前已停止维护。这四者代表了从底层样式引擎到高层设计系统约束的不同抽象层级,开发者需根据项目对灵活性、约束性及维护状态的需求进行选择。
在 React 生态中,样式解决方案的选择直接影响代码的可维护性、性能以及团队协作效率。emotion 和 styled-components 提供了底层样式引擎,而 theme-ui 和 rebass 则在此基础上构建了更高层的设计系统约束。本文将深入对比它们的核心机制、API 设计以及维护状态,帮助架构师做出明智决策。
styled-components 强制使用 tagged template literals(模板字符串)。
// styled-components: 模板字符串
const Button = styled.button`
background: ${props => props.primary ? 'blue' : 'white'};
color: ${props => props.primary ? 'white' : 'blue'};
padding: 8px 16px;
`;
emotion 同时支持模板字符串和对象样式。
css prop,无需包装组件即可应用样式。// emotion: 对象样式 + css prop
import { css } from '@emotion/react';
function Button({ primary }) {
return (
<button css={css({
background: primary ? 'blue' : 'white',
color: primary ? 'white' : 'blue',
padding: '8px 16px'
})}>
Click Me
</button>
);
}
theme-ui 基于 emotion,使用 sx 属性。
sx 属性直接映射 Theme 对象中的键值。// theme-ui: sx prop
import { Box } from 'theme-ui';
function Button({ primary }) {
return (
<Box
as="button"
sx={{
bg: primary ? 'primary' : 'background',
color: primary ? 'background' : 'text',
p: 2
}}
>
Click Me
</Box>
);
}
rebass 使用组件属性传递样式值。
color、p 等 props 直接控制样式。// rebass: 样式 Props (Legacy)
import { Button } from 'rebass';
// 已弃用,仅展示历史写法
function LegacyButton({ primary }) {
return (
<Button
bg={primary ? 'blue' : 'white'}
color={primary ? 'white' : 'blue'}
p={2}
>
Click Me
</Button>
);
}
styled-components 提供 ThemeProvider。
props.theme 访问,灵活但容易导致团队间结构不一致。// styled-components: 自由主题
const theme = { colors: { primary: 'blue' } };
const Title = styled.h1`
color: ${props => props.theme.colors.primary};
`;
emotion 同样提供 ThemeProvider。
css prop 使用更灵活。// emotion: 自由主题
import { useTheme } from '@emotion/react';
function Title() {
const theme = useTheme();
return <h1 css={{ color: theme.colors.primary }}>Hello</h1>;
}
theme-ui 强制使用 Standard Theme Specification。
colors.primary, space[0])是标准化的。// theme-ui: 标准主题
const theme = {
colors: { primary: 'blue' },
space: [0, 4, 8, 16]
};
// 组件自动读取 theme.space[2]
<Box p={2}>Content</Box>
rebass 基于早期的 styled-system 规范。
// rebass: 旧版规范 (Legacy)
// 类似 theme-ui,但不再推荐
<Box p={2}>Content</Box>
emotion 和 styled-components 均处于活跃维护状态。
theme-ui 处于维护模式,更新频率较低。
emotion,底层依赖稳定。rebass 已官方标记为 不再维护 (Archived)。
// 风险提示
// import { Box } from 'rebass'; // ❌ 不要这样做
// import { Box } from 'theme-ui'; // ✅ 更安全的替代
emotion 在 SSR 方面表现优异。
extractCritical 等工具,避免样式闪烁(FOUC)。// emotion: SSR 提取
import { extractCritical } from '@emotion/server';
const { ids, css } = extractCritical(html);
styled-components 需要配合 ServerStyleSheet。
// styled-components: SSR
const sheet = new ServerStyleSheet();
const html = renderToString(sheet.collectStyles(<App />));
const styleTags = sheet.getStyleTags();
theme-ui 继承 emotion 的 SSR 能力。
ThemeProvider。// theme-ui: SSR
// 沿用 emotion 的 SSR 配置即可
rebass 依赖旧版 styled-system。
// rebass: SSR
// 旧版配置,可能已过时
| 特性 | emotion | styled-components | theme-ui | rebass |
|---|---|---|---|---|
| 底层引擎 | 自研 | 自研 | 基于 Emotion | 基于 Styled-System |
| 样式语法 | 对象 / 模板 | 模板字符串 | sx 属性 | 组件 Props |
| 主题规范 | 自由 | 自由 | 标准化约束 | 标准化约束 (旧) |
| 维护状态 | ✅ 活跃 | ✅ 活跃 | ⚠️ 维护模式 | ❌ 已弃用 |
| TS 支持 | ✅ 优秀 | ✅ 优秀 | ✅ 优秀 | ⚠️ 一般 |
| 推荐场景 | 组件库 / 高灵活 | 企业应用 / 生态依赖 | 设计系统 / 快速规范 | ❌ 不建议使用 |
emotion 是技术自由度最高的选择 🕊️。如果你正在构建一个需要发布给他人使用的组件库,或者团队对 TypeScript 和对象样式有强烈偏好,它是首选。它的底层能力最强,且被 theme-ui 等库验证为可靠基石。
styled-components 是生态最成熟的选择 🌳。如果团队希望减少配置时间,依赖丰富的第三方主题和教程,且偏好 CSS -like 语法,它依然非常稳健。适合大多数标准 Web 应用开发。
theme-ui 是设计系统落地的捷径 🎨。如果你希望强制团队遵循统一的空间、颜色和排版规范,减少样式争论,它是一个很好的上层抽象。但需接受其更新节奏较慢的现实。
rebass 是历史遗产 🏛️。仅存在于旧项目中。如果遇到它,请将其视为技术债务,计划迁移到 theme-ui 或原生 emotion 方案。
最终思考:CSS-in-JS 的核心价值在于样式与组件的共定位。选择 emotion 或 styled-components 作为底层引擎是安全的,而是否引入 theme-ui 这样的约束层,取决于你的团队是需要“自由”还是“规范”。
选择 styled-components 如果你偏好模板字符串语法,希望拥有最大的社区生态和插件支持,或者团队已经熟悉其开发模式。它在开发者体验(DX)上非常优秀,适合需要快速原型开发且依赖丰富第三方主题资源的企业级应用。
选择 emotion 如果你需要极高的灵活性,偏好对象样式语法,或者正在开发一个需要强大 SSR 支持和样式提取能力的组件库。它在性能优化和 API 多样性(css prop、styled、class names)方面表现最佳,适合对样式控制粒度要求极高的场景。
选择 theme-ui 如果你希望快速建立一套基于约束的设计系统,且不想手动处理底层 CSS-in-JS 配置。它基于 emotion,提供了标准化的 sx 属性和主题结构,适合需要统一视觉规范、减少样式决策疲劳的中大型团队协作项目。
不建议在新项目中选择 rebass。该库已 officially 停止维护(Archived),不再接收功能更新或安全修复。如果你正在维护旧项目,建议制定迁移计划,转向 theme-ui 或其他现代组件系统,以避免技术债务和潜在的安全风险。
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.