styled-components provides a comprehensive theming system built on React Context, enabling consistent design systems across applications with theme providers, consumers, and utilities.
The ThemeProvider component makes a theme available to all styled components in the component tree via React Context.
declare const ThemeProvider: React.ComponentType<{
theme: DefaultTheme | ((outerTheme?: DefaultTheme) => DefaultTheme);
children?: React.ReactNode;
}>;
interface DefaultTheme {}Usage Examples:
import { ThemeProvider } from 'styled-components';
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
success: '#28a745',
danger: '#dc3545',
warning: '#ffc107',
info: '#17a2b8',
light: '#f8f9fa',
dark: '#343a40',
},
fonts: {
main: '"Helvetica Neue", Helvetica, Arial, sans-serif',
mono: '"Fira Code", "Courier New", monospace',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
},
breakpoints: {
mobile: '768px',
tablet: '1024px',
desktop: '1200px',
},
};
function App() {
return (
<ThemeProvider theme={theme}>
<MainLayout />
</ThemeProvider>
);
}// Theme that extends parent theme
const darkTheme = (outerTheme) => ({
...outerTheme,
colors: {
...outerTheme.colors,
background: '#1a1a1a',
text: '#ffffff',
surface: '#2d2d2d',
},
});
function DarkModeWrapper({ children }) {
return (
<ThemeProvider theme={darkTheme}>
{children}
</ThemeProvider>
);
}const baseTheme = {
colors: { primary: '#007bff' },
spacing: { md: '16px' }
};
const buttonTheme = (outerTheme) => ({
...outerTheme,
button: {
borderRadius: '4px',
fontSize: '14px',
}
});
function App() {
return (
<ThemeProvider theme={baseTheme}>
<Header />
<main>
<ThemeProvider theme={buttonTheme}>
<ButtonSection />
</ThemeProvider>
</main>
</ThemeProvider>
);
}React hook for accessing the current theme in functional components.
function useTheme(): DefaultTheme;Usage Examples:
import { useTheme } from 'styled-components';
function ThemedComponent() {
const theme = useTheme(); // Throws error if no ThemeProvider found
return (
<div style={{
backgroundColor: theme.colors.primary,
padding: theme.spacing.md
}}>
Themed content
</div>
);
}
// Safe usage with error boundary or conditional rendering
function ConditionalThemedComponent() {
try {
const theme = useTheme();
return (
<div style={{
backgroundColor: theme.colors.primary,
padding: theme.spacing.md
}}>
Themed content
</div>
);
} catch (error) {
return <div>No theme available</div>;
}
}
// Custom hook with type assertion
function useAppTheme() {
const theme = useTheme(); // Already throws if no theme
return theme as AppTheme;
}Higher-order component that injects the theme as a prop.
function withTheme<T extends React.ComponentType<any>>(
Component: T
): React.ForwardRefExoticComponent<
React.PropsWithoutRef<React.ComponentProps<T>> & React.RefAttributes<T>
>;Usage Examples:
import { withTheme } from 'styled-components';
interface Props {
title: string;
theme: DefaultTheme;
}
function ThemedHeader({ title, theme }: Props) {
return (
<h1 style={{
color: theme.colors.primary,
fontFamily: theme.fonts.main
}}>
{title}
</h1>
);
}
const ThemedHeaderWithTheme = withTheme(ThemedHeader);
// Usage - theme prop is automatically injected
<ThemedHeaderWithTheme title="Welcome" />Direct access to the theme context for advanced use cases.
declare const ThemeContext: React.Context<DefaultTheme | undefined>;
declare const ThemeConsumer: React.Consumer<DefaultTheme | undefined>;Usage Examples:
import { ThemeContext, ThemeConsumer } from 'styled-components';
// Using ThemeContext directly
function DirectThemeAccess() {
const theme = React.useContext(ThemeContext);
return <div>Current primary color: {theme?.colors.primary}</div>;
}
// Using ThemeConsumer
function ThemeConsumerExample() {
return (
<ThemeConsumer>
{theme => (
<div style={{ backgroundColor: theme?.colors.background }}>
Consumer content
</div>
)}
</ThemeConsumer>
);
}const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
color: white;
padding: ${props => props.theme.spacing.md};
font-family: ${props => props.theme.fonts.main};
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: ${props => props.theme.colors.primaryHover || props.theme.colors.primary};
opacity: 0.9;
}
`;interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
}
const Button = styled.button<ButtonProps>`
background-color: ${props => {
switch (props.variant) {
case 'primary': return props.theme.colors.primary;
case 'secondary': return props.theme.colors.secondary;
case 'danger': return props.theme.colors.danger;
default: return props.theme.colors.primary;
}
}};
padding: ${props => {
const spacing = props.theme.spacing;
switch (props.size) {
case 'small': return `${spacing.xs} ${spacing.sm}`;
case 'large': return `${spacing.lg} ${spacing.xl}`;
default: return `${spacing.sm} ${spacing.md}`;
}
}};
font-size: ${props => {
switch (props.size) {
case 'small': return '12px';
case 'large': return '18px';
default: return '14px';
}
}};
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: ${props => props.theme.fonts.main};
`;const ResponsiveContainer = styled.div`
padding: ${props => props.theme.spacing.md};
@media (max-width: ${props => props.theme.breakpoints.mobile}) {
padding: ${props => props.theme.spacing.sm};
}
@media (min-width: ${props => props.theme.breakpoints.desktop}) {
padding: ${props => props.theme.spacing.xl};
}
`;// styled-components.d.ts
import 'styled-components';
interface AppTheme {
colors: {
primary: string;
secondary: string;
background: string;
text: string;
error: string;
success: string;
};
fonts: {
main: string;
mono: string;
};
spacing: {
xs: string;
sm: string;
md: string;
lg: string;
xl: string;
};
breakpoints: {
mobile: string;
tablet: string;
desktop: string;
};
zIndex: {
dropdown: number;
modal: number;
tooltip: number;
};
}
declare module 'styled-components' {
export interface DefaultTheme extends AppTheme {}
}// Now all styled components have full TypeScript support for theme
const TypedButton = styled.button`
background-color: ${props => props.theme.colors.primary}; // ✅ Type-safe
padding: ${props => props.theme.spacing.md}; // ✅ Type-safe
z-index: ${props => props.theme.zIndex.dropdown}; // ✅ Type-safe
`;
// TypeScript will catch errors
const ErrorButton = styled.button`
background-color: ${props => props.theme.colors.primaryColor}; // ❌ TypeScript error
`;// Theme utility functions
export const getColor = (theme: DefaultTheme, color: keyof DefaultTheme['colors']) => {
return theme.colors[color];
};
export const getSpacing = (theme: DefaultTheme, size: keyof DefaultTheme['spacing']) => {
return theme.spacing[size];
};
export const media = {
mobile: (content: TemplateStringsArray) => css`
@media (max-width: ${props => props.theme.breakpoints.mobile}) {
${content}
}
`,
tablet: (content: TemplateStringsArray) => css`
@media (max-width: ${props => props.theme.breakpoints.tablet}) {
${content}
}
`,
desktop: (content: TemplateStringsArray) => css`
@media (min-width: ${props => props.theme.breakpoints.desktop}) {
${content}
}
`,
};
// Usage
const UtilityButton = styled.button`
background-color: ${props => getColor(props.theme, 'primary')};
padding: ${props => getSpacing(props.theme, 'md')};
${media.mobile`
font-size: 14px;
`}
`;const lightTheme: AppTheme = {
colors: {
background: '#ffffff',
text: '#333333',
primary: '#007bff',
// ...
},
// ...
};
const darkTheme: AppTheme = {
colors: {
background: '#1a1a1a',
text: '#ffffff',
primary: '#4dabf7',
// ...
},
// ...
};
function App() {
const [isDark, setIsDark] = React.useState(false);
return (
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<GlobalStyle />
<ThemeToggle onClick={() => setIsDark(!isDark)} />
<MainContent />
</ThemeProvider>
);
}// Custom theme hook with fallbacks
function useAppTheme() {
const theme = useTheme();
// Provide default theme if none exists
const defaultTheme: AppTheme = {
colors: { /* defaults */ },
fonts: { /* defaults */ },
// ...
};
return theme || defaultTheme;
}
// Higher-order component for theme requirements
function withRequiredTheme<Props>(Component: React.ComponentType<Props>) {
return function ThemedComponent(props: Props) {
const theme = useTheme();
if (!theme) {
throw new Error('Component requires a theme but no ThemeProvider was found');
}
return <Component {...props} />;
};
}interface ThemeProviderProps {
children: React.ReactNode;
themeName?: string;
}
const themes = {
light: lightTheme,
dark: darkTheme,
blue: blueTheme,
green: greenTheme,
};
function DynamicThemeProvider({ children, themeName = 'light' }: ThemeProviderProps) {
const [currentTheme, setCurrentTheme] = React.useState(themes[themeName]);
React.useEffect(() => {
setCurrentTheme(themes[themeName] || themes.light);
}, [themeName]);
return (
<ThemeProvider theme={currentTheme}>
{children}
</ThemeProvider>
);
}