Emotion Theming is a CSS-in-JS theming solution for React applications, inspired by styled-components. It provides a React context-based theming system that integrates seamlessly with Emotion's styling ecosystem, enabling developers to create consistent design systems and dynamic themes across their applications.
npm install emotion-themingimport { ThemeProvider, withTheme, useTheme } from "emotion-theming";For CommonJS:
const { ThemeProvider, withTheme, useTheme } = require("emotion-theming");import React from "react";
import { ThemeProvider, useTheme, withTheme } from "emotion-theming";
import styled from "@emotion/styled";
// Define your theme
const theme = {
colors: {
primary: "hotpink",
secondary: "turquoise",
},
fonts: {
body: "system-ui, sans-serif",
heading: "Georgia, serif",
},
};
// Use theme in styled components (automatic injection)
const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
color: white;
font-family: ${props => props.theme.fonts.body};
`;
// Use theme with useTheme hook
function ThemedText() {
const theme = useTheme();
return (
<p style={{ color: theme.colors.secondary }}>
Themed text using useTheme hook
</p>
);
}
// Use theme with withTheme HOC
const ThemedDiv = withTheme(({ theme, children }) => (
<div style={{ fontFamily: theme.fonts.heading }}>
{children}
</div>
));
// Wrap your app with ThemeProvider
function App() {
return (
<ThemeProvider theme={theme}>
<ThemedDiv>
<Button>Click me</Button>
<ThemedText />
</ThemedDiv>
</ThemeProvider>
);
}Emotion Theming is built around several key patterns:
React component that injects theme objects into the component tree via React context. Supports both object-based themes and function-based theme transformations for nested providers.
/**
* React component that provides theme context to descendant components
* @param props - Component props including theme and children
* @returns React element that provides theme context
*/
function ThemeProvider<Theme>(
props: ThemeProviderProps<Theme>
): React.ReactElement;
interface ThemeProviderProps<Theme> {
/** Theme object or function that transforms parent theme */
theme: Partial<Theme> | ((outerTheme: Theme) => Theme);
/** Child components that will receive theme context */
children?: React.ReactNode;
}Usage Examples:
import React from "react";
import { ThemeProvider } from "emotion-theming";
// Object-based theme
const baseTheme = {
colors: { primary: "blue", secondary: "green" },
spacing: { small: "8px", medium: "16px" }
};
// Function-based theme for nested providers
const darkTheme = (parentTheme) => ({
...parentTheme,
colors: {
...parentTheme.colors,
primary: "darkblue",
background: "black"
}
});
function App() {
return (
<ThemeProvider theme={baseTheme}>
<div>
{/* Nested provider with theme transformation */}
<ThemeProvider theme={darkTheme}>
<DarkModeSection />
</ThemeProvider>
</div>
</ThemeProvider>
);
}React hook that returns the current theme object from the nearest ThemeProvider ancestor. Causes component re-renders when theme changes.
/**
* React hook that returns the current theme from context
* @returns Current theme object
*/
function useTheme<Theme>(): Theme;Usage Examples:
import React from "react";
import { useTheme } from "emotion-theming";
function ThemedComponent() {
const theme = useTheme();
return (
<div style={{
color: theme.colors.primary,
padding: theme.spacing.medium,
fontFamily: theme.fonts.body
}}>
Content styled with theme
</div>
);
}
// With TypeScript for better type safety
interface MyTheme {
colors: { primary: string; secondary: string; };
spacing: { small: string; medium: string; };
}
function TypedThemedComponent() {
const theme = useTheme<MyTheme>();
// theme is now properly typed as MyTheme
return (
<button style={{ backgroundColor: theme.colors.primary }}>
Typed theme usage
</button>
);
}Higher-order component that injects the current theme as a prop into the wrapped component. Useful for class components and complex component wrapping scenarios.
/**
* Higher-order component that injects theme as a prop
* @param component - React component to wrap with theme injection
* @returns Component with theme prop injected
*/
function withTheme<C extends React.ComponentType<any>>(
component: C
): React.FC<AddOptionalTo<PropsOf<C>, 'theme'>>;Usage Examples:
import React from "react";
import { withTheme } from "emotion-theming";
// Class component usage
class ClassButton extends React.Component {
render() {
const { theme, children, ...otherProps } = this.props;
return (
<button
style={{
backgroundColor: theme.colors.primary,
color: theme.colors.text,
padding: theme.spacing.medium
}}
{...otherProps}
>
{children}
</button>
);
}
}
const ThemedClassButton = withTheme(ClassButton);
// Functional component usage (alternative to useTheme)
const FunctionalButton = ({ theme, children, ...props }) => (
<button
style={{
backgroundColor: theme.colors.secondary,
borderRadius: theme.borderRadius
}}
{...props}
>
{children}
</button>
);
const ThemedFunctionalButton = withTheme(FunctionalButton);
// Usage in JSX
function App() {
return (
<div>
<ThemedClassButton>Class Component Button</ThemedClassButton>
<ThemedFunctionalButton>Functional Component Button</ThemedFunctionalButton>
</div>
);
}Emotion Theming includes development-time validation with helpful error messages:
// Theme must be an object or function
<ThemeProvider theme={null} /> // Error: Please make your theme prop a plain object
// Theme functions must return objects
<ThemeProvider theme={() => null} /> // Error: Please return an object from your theme function
// Arrays are not valid themes
<ThemeProvider theme={[]} /> // Error: Please make your theme prop a plain objectinterface ThemeProviderProps<Theme> {
theme: Partial<Theme> | ((outerTheme: Theme) => Theme);
children?: React.ReactNode;
}
interface EmotionTheming<Theme> {
ThemeProvider(props: ThemeProviderProps<Theme>): React.ReactElement;
withTheme<C extends React.ComponentType<any>>(
component: C
): React.FC<AddOptionalTo<PropsOf<C>, 'theme'>>;
}
type PropsOf<
C extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>
> = JSX.LibraryManagedAttributes<C, React.ComponentPropsWithRef<C>>;
type Omit<T, U> = T extends any ? Pick<T, Exclude<keyof T, U>> : never;
type AddOptionalTo<T, U> = Omit<T, U> & Partial<Pick<T, Extract<keyof T, U>>>;Emotion Theming integrates seamlessly with other Emotion packages:
import styled from "@emotion/styled";
import { ThemeProvider } from "emotion-theming";
// Theme is automatically injected into styled components
const Button = styled.button`
background-color: ${props => props.theme.colors.primary};
padding: ${props => props.theme.spacing.medium};
`;
// No need to manually pass theme - it's injected automatically
<ThemeProvider theme={theme}>
<Button>Automatically themed</Button>
</ThemeProvider>/** @jsx jsx */
import { jsx } from "@emotion/core";
import { ThemeProvider } from "emotion-theming";
function MyComponent() {
return (
<div
css={theme => ({
color: theme.colors.primary,
fontSize: theme.typography.body
})}
>
CSS prop with theme function
</div>
);
}
<ThemeProvider theme={theme}>
<MyComponent />
</ThemeProvider>const lightTheme = { mode: 'light', colors: { bg: 'white', text: 'black' } };
const darkTheme = { mode: 'dark', colors: { bg: 'black', text: 'white' } };
function App({ isDark }) {
return (
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<MyApp />
</ThemeProvider>
);
}const baseTheme = {
colors: { primary: 'blue', secondary: 'green' },
spacing: { small: '8px', medium: '16px' }
};
function App() {
return (
<ThemeProvider theme={baseTheme}>
<Header />
{/* Override specific colors for this section */}
<ThemeProvider theme={parent => ({
...parent,
colors: { ...parent.colors, primary: 'red' }
})}>
<AlertSection />
</ThemeProvider>
</ThemeProvider>
);
}// ✅ Good: Theme object is hoisted and stable
const theme = { colors: { primary: 'blue' } };
function App() {
return (
<ThemeProvider theme={theme}>
<Content />
</ThemeProvider>
);
}
// ❌ Bad: Theme object is recreated on every render
function BadApp() {
return (
<ThemeProvider theme={{ colors: { primary: 'blue' } }}>
<Content />
</ThemeProvider>
);
}