Storybook addon that enables theme switching functionality within the preview environment with support for multiple theming strategies
—
Theme switching using JSX provider components and theme objects. This approach is designed for component libraries like styled-components, emotion, Material-UI, and other CSS-in-JS solutions that use theme providers.
Creates a decorator that wraps stories with a theme provider component and passes theme objects to the provider.
/**
* Creates a decorator for JSX provider-based theme switching
* @param config - Configuration object specifying provider, themes, and options
* @returns Storybook decorator function
*/
function withThemeFromJSXProvider<TRenderer extends Renderer = any>(
config: ProviderStrategyConfiguration
): DecoratorFunction<TRenderer>;
interface ProviderStrategyConfiguration {
/** JSX provider component that accepts a theme prop */
Provider?: any;
/** Global styles component to render alongside the provider */
GlobalStyles?: any;
/** Name of the default theme */
defaultTheme?: string;
/** Mapping of theme names to theme objects */
themes?: Record<string, any>;
}Usage Examples:
import { withThemeFromJSXProvider } from '@storybook/addon-themes';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './themes';
// Styled-components example
export const decorators = [
withThemeFromJSXProvider({
themes: {
light: lightTheme,
dark: darkTheme,
},
defaultTheme: 'light',
Provider: ThemeProvider,
}),
];
// Material-UI example
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
const lightTheme = createTheme({
palette: {
mode: 'light',
},
});
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
});
export const decorators = [
withThemeFromJSXProvider({
themes: {
light: lightTheme,
dark: darkTheme,
},
defaultTheme: 'light',
Provider: ThemeProvider,
GlobalStyles: CssBaseline,
}),
];
// Emotion example
import { ThemeProvider } from '@emotion/react';
import { Global, css } from '@emotion/react';
const GlobalStyles = () => (
<Global
styles={css`
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
`}
/>
);
export const decorators = [
withThemeFromJSXProvider({
themes: {
brand: { primary: '#007bff', secondary: '#6c757d' },
corporate: { primary: '#28a745', secondary: '#17a2b8' },
},
defaultTheme: 'brand',
Provider: ThemeProvider,
GlobalStyles: GlobalStyles,
}),
];The decorator automatically:
theme propTheme objects can have any structure that your styling library expects:
// Styled-components theme example
const lightTheme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
background: '#ffffff',
text: '#212529',
},
spacing: {
small: '8px',
medium: '16px',
large: '24px',
},
breakpoints: {
mobile: '768px',
desktop: '1024px',
},
};
// Material-UI theme example (using createTheme)
const muiTheme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
typography: {
fontFamily: 'Roboto, Arial, sans-serif',
},
});When only one theme is provided, the decorator still wraps stories with the provider:
export const decorators = [
withThemeFromJSXProvider({
themes: {
default: singleTheme,
},
Provider: ThemeProvider,
}),
];The GlobalStyles component is rendered alongside the provider, perfect for CSS resets and global styles:
// Styled-components GlobalStyle
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
body {
margin: 0;
font-family: ${props => props.theme.fonts.primary};
background-color: ${props => props.theme.colors.background};
color: ${props => props.theme.colors.text};
}
`;
export const decorators = [
withThemeFromJSXProvider({
themes: { light: lightTheme, dark: darkTheme },
defaultTheme: 'light',
Provider: ThemeProvider,
GlobalStyles: GlobalStyle,
}),
];import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
body {
background-color: ${props => props.theme.background};
color: ${props => props.theme.text};
}
`;
const themes = {
light: {
background: '#ffffff',
text: '#000000',
primary: '#007bff',
},
dark: {
background: '#121212',
text: '#ffffff',
primary: '#66aaff',
},
};
export const decorators = [
withThemeFromJSXProvider({
themes,
defaultTheme: 'light',
Provider: ThemeProvider,
GlobalStyles: GlobalStyle,
}),
];import { ThemeProvider } from '@emotion/react';
import { Global, css } from '@emotion/react';
const GlobalStyles = ({ theme }) => (
<Global
styles={css`
body {
background-color: ${theme.background};
color: ${theme.text};
}
`}
/>
);
export const decorators = [
withThemeFromJSXProvider({
themes: emotionThemes,
defaultTheme: 'light',
Provider: ThemeProvider,
GlobalStyles: GlobalStyles,
}),
];import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
const lightTheme = createTheme({
palette: { mode: 'light' },
});
const darkTheme = createTheme({
palette: { mode: 'dark' },
});
export const decorators = [
withThemeFromJSXProvider({
themes: {
light: lightTheme,
dark: darkTheme,
},
defaultTheme: 'light',
Provider: ThemeProvider,
GlobalStyles: CssBaseline,
}),
];Install with Tessl CLI
npx tessl i tessl/npm-storybook--addon-themes