Storybook addon that enables theme switching functionality within the preview environment with support for multiple theming strategies
—
Utilities for building custom theme decorators when the built-in decorators don't meet specific requirements. These helper functions provide access to the addon's theme state and enable custom theming implementations.
Collection of utility functions for creating custom theme decorators.
namespace DecoratorHelpers {
function pluckThemeFromContext(context: StoryContext): string;
function initializeThemeState(themeNames: string[], defaultTheme: string): void;
function useThemeParameters(context?: StoryContext): ThemesParameters;
}Extracts the currently selected theme name from the Storybook story context.
/**
* Extracts the currently selected theme name from story context
* @param context - Storybook story context object
* @returns The name of the currently selected theme
*/
function pluckThemeFromContext(context: StoryContext): string;Usage Example:
import { DecoratorHelpers } from '@storybook/addon-themes';
const { pluckThemeFromContext } = DecoratorHelpers;
export const myCustomDecorator = ({ themes, defaultTheme }) => {
return (storyFn, context) => {
const selectedTheme = pluckThemeFromContext(context);
const currentTheme = selectedTheme || defaultTheme;
// Apply theme-specific logic
document.documentElement.style.setProperty('--current-theme', currentTheme);
return storyFn();
};
};Registers themes with the addon state, enabling the theme switcher UI in the Storybook toolbar.
/**
* Registers themes with the addon state for toolbar integration
* @param themeNames - Array of theme names to register
* @param defaultTheme - Name of the default theme
*/
function initializeThemeState(themeNames: string[], defaultTheme: string): void;Usage Example:
import { DecoratorHelpers } from '@storybook/addon-themes';
const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;
export const withCustomTheming = ({ themes, defaultTheme }) => {
// Register themes with the addon
initializeThemeState(Object.keys(themes), defaultTheme);
return (storyFn, context) => {
const selectedTheme = pluckThemeFromContext(context);
const theme = themes[selectedTheme] || themes[defaultTheme];
// Custom theming logic here
applyCustomTheme(theme);
return storyFn();
};
};⚠️ Deprecated: This function is deprecated and will log a deprecation warning when used. Access theme parameters directly via the context instead.
/**
* @deprecated Access parameters via context.parameters.themes instead
* Returns theme parameters for the current story
* @param context - Optional story context
* @returns Theme parameters object
*/
function useThemeParameters(context?: StoryContext): ThemesParameters;Modern Alternative:
// Instead of useThemeParameters()
const { themeOverride } = context.parameters.themes ?? {};
// Old deprecated way
const { themeOverride } = useThemeParameters(context);import { DecoratorHelpers } from '@storybook/addon-themes';
const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;
export const withCSSCustomProperties = ({ themes, defaultTheme }) => {
initializeThemeState(Object.keys(themes), defaultTheme);
return (storyFn, context) => {
const selectedTheme = pluckThemeFromContext(context);
const { themeOverride } = context.parameters.themes ?? {};
const currentTheme = themeOverride || selectedTheme || defaultTheme;
const themeValues = themes[currentTheme];
// Apply CSS custom properties
const root = document.documentElement;
Object.entries(themeValues).forEach(([key, value]) => {
root.style.setProperty(`--theme-${key}`, String(value));
});
return storyFn();
};
};
// Usage
export const decorators = [
withCSSCustomProperties({
themes: {
light: { background: '#ffffff', text: '#000000' },
dark: { background: '#000000', text: '#ffffff' },
},
defaultTheme: 'light',
}),
];import { DecoratorHelpers } from '@storybook/addon-themes';
import { useTheme } from 'vuetify';
const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;
export const withVuetifyTheme = ({ themes, defaultTheme }) => {
initializeThemeState(Object.keys(themes), defaultTheme);
return (story, context) => {
const selectedTheme = pluckThemeFromContext(context);
const { themeOverride } = context.parameters.themes ?? {};
const selected = themeOverride || selectedTheme || defaultTheme;
return {
components: { story },
setup() {
const theme = useTheme();
theme.global.name.value = themes[selected];
return { theme };
},
template: `<v-app><story /></v-app>`,
};
};
};
// Usage in .storybook/preview.js
export const decorators = [
withVuetifyTheme({
themes: {
light: 'light',
dark: 'dark',
'high contrast': 'highContrast',
},
defaultTheme: 'light',
}),
];import { DecoratorHelpers } from '@storybook/addon-themes';
import { useEffect } from 'react';
const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;
export const withAnimatedBodyClass = ({ themes, defaultTheme, transitionDuration = 300 }) => {
initializeThemeState(Object.keys(themes), defaultTheme);
return (storyFn, context) => {
const selectedTheme = pluckThemeFromContext(context);
const { themeOverride } = context.parameters.themes ?? {};
useEffect(() => {
const currentTheme = themeOverride || selectedTheme || defaultTheme;
const body = document.body;
// Add transition
body.style.transition = `background-color ${transitionDuration}ms ease, color ${transitionDuration}ms ease`;
// Remove old theme classes
Object.values(themes).forEach(className => {
body.classList.remove(className as string);
});
// Add new theme class
body.classList.add(themes[currentTheme]);
return () => {
body.style.transition = '';
};
}, [themeOverride, selectedTheme]);
return storyFn();
};
};import { DecoratorHelpers } from '@storybook/addon-themes';
const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;
export const withMultiContextTheme = ({ themes, defaultTheme, contexts }) => {
initializeThemeState(Object.keys(themes), defaultTheme);
return (storyFn, context) => {
const selectedTheme = pluckThemeFromContext(context);
const { themeOverride } = context.parameters.themes ?? {};
const currentTheme = themeOverride || selectedTheme || defaultTheme;
const themeConfig = themes[currentTheme];
// Apply theme to multiple contexts
contexts.forEach(({ selector, property, value }) => {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
element.style[property] = themeConfig[value];
});
});
return storyFn();
};
};
// Usage
export const decorators = [
withMultiContextTheme({
themes: {
light: { bg: '#fff', text: '#000', accent: '#007bff' },
dark: { bg: '#000', text: '#fff', accent: '#66aaff' },
},
defaultTheme: 'light',
contexts: [
{ selector: 'body', property: 'backgroundColor', value: 'bg' },
{ selector: 'body', property: 'color', value: 'text' },
{ selector: '.accent', property: 'color', value: 'accent' },
],
}),
];All custom decorators should support theme overrides at the story level:
export const MyStoryWithOverride = {
parameters: {
themes: {
themeOverride: 'dark' // Force this story to use dark theme
}
}
};Access the override in your decorator:
const { themeOverride } = context.parameters.themes ?? {};
const finalTheme = themeOverride || selectedTheme || defaultTheme;Install with Tessl CLI
npx tessl i tessl/npm-storybook--addon-themes