主题定制 #
基本概念 #
Emotion 提供了强大的主题系统,通过 ThemeProvider 组件可以在应用中共享主题数据,实现统一的样式管理和主题切换功能。
基本用法 #
定义主题 #
创建主题对象:
jsx
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
success: '#28a745',
danger: '#dc3545',
warning: '#ffc107',
info: '#17a2b8',
light: '#f8f9fa',
dark: '#343a40',
white: '#ffffff',
black: '#000000',
text: '#333333',
textMuted: '#6c757d',
background: '#ffffff',
},
fontSizes: {
xs: '12px',
sm: '14px',
md: '16px',
lg: '18px',
xl: '20px',
xxl: '24px',
xxxl: '32px',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
xxl: '48px',
},
borderRadius: {
sm: '4px',
md: '8px',
lg: '12px',
full: '9999px',
},
shadows: {
sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
md: '0 4px 6px rgba(0, 0, 0, 0.1)',
lg: '0 10px 15px rgba(0, 0, 0, 0.1)',
xl: '0 20px 25px rgba(0, 0, 0, 0.15)',
},
breakpoints: {
sm: '576px',
md: '768px',
lg: '992px',
xl: '1200px',
},
}
使用 ThemeProvider #
jsx
import { ThemeProvider } from '@emotion/react'
import styled from '@emotion/styled'
const Button = styled.button`
padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
background-color: ${props => props.theme.colors.primary};
color: ${props => props.theme.colors.white};
border: none;
border-radius: ${props => props.theme.borderRadius.sm};
font-size: ${props => props.theme.fontSizes.md};
cursor: pointer;
&:hover {
opacity: 0.9;
}
`
function App() {
return (
<ThemeProvider theme={theme}>
<Button>Click me</Button>
</ThemeProvider>
)
}
深色模式 #
定义双主题 #
jsx
const lightTheme = {
colors: {
primary: '#007bff',
background: '#ffffff',
surface: '#f8f9fa',
text: '#333333',
textMuted: '#6c757d',
border: '#dee2e6',
},
mode: 'light',
}
const darkTheme = {
colors: {
primary: '#4da3ff',
background: '#1a1a1a',
surface: '#2d2d2d',
text: '#ffffff',
textMuted: '#adb5bd',
border: '#495057',
},
mode: 'dark',
}
主题切换组件 #
jsx
import { useState } from 'react'
import { ThemeProvider } from '@emotion/react'
import styled from '@emotion/styled'
const Container = styled.div`
min-height: 100vh;
background-color: ${props => props.theme.colors.background};
color: ${props => props.theme.colors.text};
transition: background-color 0.3s ease, color 0.3s ease;
`
const ToggleButton = styled.button`
position: fixed;
top: 20px;
right: 20px;
padding: 10px 20px;
background-color: ${props => props.theme.colors.primary};
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
`
function App() {
const [isDark, setIsDark] = useState(false)
return (
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<Container>
<ToggleButton onClick={() => setIsDark(!isDark)}>
{isDark ? '☀️ Light' : '🌙 Dark'}
</ToggleButton>
<h1>Hello World</h1>
<p>Current mode: {isDark ? 'Dark' : 'Light'}</p>
</Container>
</ThemeProvider>
)
}
跟随系统主题 #
jsx
import { useState, useEffect } from 'react'
import { ThemeProvider } from '@emotion/react'
function App() {
const [isDark, setIsDark] = useState(() => {
if (typeof window !== 'undefined') {
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
return false
})
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = (e) => setIsDark(e.matches)
mediaQuery.addEventListener('change', handleChange)
return () => mediaQuery.removeEventListener('change', handleChange)
}, [])
return (
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<AppContent />
</ThemeProvider>
)
}
useTheme Hook #
访问主题 #
在组件中使用 useTheme Hook 访问主题:
jsx
/** @jsxImportSource @emotion/react */
import { css, useTheme } from '@emotion/react'
function Card({ children }) {
const theme = useTheme()
return (
<div
css={css`
padding: ${theme.spacing.md};
background-color: ${theme.colors.surface};
border-radius: ${theme.borderRadius.md};
box-shadow: ${theme.shadows.md};
`}
>
{children}
</div>
)
}
条件样式 #
根据主题模式调整样式:
jsx
/** @jsxImportSource @emotion/react */
import { css, useTheme } from '@emotion/react'
function ThemedInput({ ...props }) {
const theme = useTheme()
return (
<input
css={css`
padding: ${theme.spacing.sm} ${theme.spacing.md};
background-color: ${theme.mode === 'dark' ? '#2d2d2d' : '#ffffff'};
color: ${theme.colors.text};
border: 1px solid ${theme.colors.border};
border-radius: ${theme.borderRadius.sm};
outline: none;
&:focus {
border-color: ${theme.colors.primary};
box-shadow: 0 0 0 3px ${theme.colors.primary}33;
}
`}
{...props}
/>
)
}
主题扩展 #
创建变体主题 #
jsx
const createTheme = (baseTheme, overrides) => ({
...baseTheme,
...overrides,
colors: {
...baseTheme.colors,
...overrides.colors,
},
})
const blueTheme = createTheme(lightTheme, {
colors: {
primary: '#007bff',
},
})
const greenTheme = createTheme(lightTheme, {
colors: {
primary: '#28a745',
},
})
const purpleTheme = createTheme(lightTheme, {
colors: {
primary: '#6f42c1',
},
})
嵌套主题 #
主题可以嵌套,内层主题会覆盖外层主题:
jsx
import { ThemeProvider } from '@emotion/react'
import styled from '@emotion/styled'
const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
`
function App() {
return (
<ThemeProvider theme={blueTheme}>
<Button>Blue Button</Button>
<ThemeProvider theme={greenTheme}>
<Button>Green Button</Button>
</ThemeProvider>
<Button>Blue Button Again</Button>
</ThemeProvider>
)
}
TypeScript 支持 #
主题类型定义 #
typescript
declare module '@emotion/react' {
export interface Theme {
colors: {
primary: string
secondary: string
success: string
danger: string
warning: string
info: string
background: string
surface: string
text: string
textMuted: string
border: string
}
fontSizes: {
xs: string
sm: string
md: string
lg: string
xl: string
}
spacing: {
xs: string
sm: string
md: string
lg: string
xl: string
}
borderRadius: {
sm: string
md: string
lg: string
full: string
}
shadows: {
sm: string
md: string
lg: string
}
mode: 'light' | 'dark'
}
}
类型安全的主题使用 #
tsx
import styled from '@emotion/styled'
import { Theme } from '@emotion/react'
const Button = styled.button<{ variant?: keyof Theme['colors'] }>`
background-color: ${props => props.theme.colors[props.variant || 'primary']};
color: white;
padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
border: none;
border-radius: ${props => props.theme.borderRadius.sm};
`
主题工具函数 #
响应式工具 #
jsx
const media = (breakpoint) => (styles) => css`
@media (min-width: ${breakpoint}) {
${styles}
}
`
const ResponsiveText = styled.p`
font-size: ${props => props.theme.fontSizes.sm};
${media(props => props.theme.breakpoints.md)(css`
font-size: ${props => props.theme.fontSizes.md};
`)}
${media(props => props.theme.breakpoints.lg)(css`
font-size: ${props => props.theme.fontSizes.lg};
`)}
`
颜色工具 #
jsx
const lighten = (color, percent) => {
const num = parseInt(color.replace('#', ''), 16)
const amt = Math.round(2.55 * percent)
const R = (num >> 16) + amt
const G = (num >> 8 & 0x00FF) + amt
const B = (num & 0x0000FF) + amt
return '#' + (
0x1000000 +
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
(B < 255 ? (B < 1 ? 0 : B) : 255)
).toString(16).slice(1)
}
const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
&:hover {
background-color: ${props => lighten(props.theme.colors.primary, 10)};
}
`
最佳实践 #
1. 主题文件组织 #
text
src/
themes/
index.ts
light.ts
dark.ts
types.ts
utils.ts
2. 默认主题 #
始终提供默认主题:
jsx
import { ThemeProvider } from '@emotion/react'
const defaultTheme = {
colors: { ... },
...
}
function App({ theme = defaultTheme }) {
return (
<ThemeProvider theme={theme}>
<AppContent />
</ThemeProvider>
)
}
3. 主题持久化 #
将主题选择保存到 localStorage:
jsx
import { useState, useEffect } from 'react'
function useTheme() {
const [theme, setTheme] = useState(() => {
const saved = localStorage.getItem('theme')
return saved ? JSON.parse(saved) : lightTheme
})
useEffect(() => {
localStorage.setItem('theme', JSON.stringify(theme))
}, [theme])
return [theme, setTheme]
}
下一步 #
掌握了主题定制后,继续学习 全局样式,了解如何添加全局 CSS 样式。
最后更新:2026-03-28