or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

css-class-theming.mdcustom-decorators.mddata-attribute-theming.mdindex.mdjsx-provider-theming.md

custom-decorators.mddocs/

0

# Custom Decorators

1

2

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.

3

4

## Capabilities

5

6

### DecoratorHelpers Namespace

7

8

Collection of utility functions for creating custom theme decorators.

9

10

```typescript { .api }

11

namespace DecoratorHelpers {

12

function pluckThemeFromContext(context: StoryContext): string;

13

function initializeThemeState(themeNames: string[], defaultTheme: string): void;

14

function useThemeParameters(context?: StoryContext): ThemesParameters;

15

}

16

```

17

18

### pluckThemeFromContext

19

20

Extracts the currently selected theme name from the Storybook story context.

21

22

```typescript { .api }

23

/**

24

* Extracts the currently selected theme name from story context

25

* @param context - Storybook story context object

26

* @returns The name of the currently selected theme

27

*/

28

function pluckThemeFromContext(context: StoryContext): string;

29

```

30

31

**Usage Example:**

32

33

```typescript

34

import { DecoratorHelpers } from '@storybook/addon-themes';

35

36

const { pluckThemeFromContext } = DecoratorHelpers;

37

38

export const myCustomDecorator = ({ themes, defaultTheme }) => {

39

return (storyFn, context) => {

40

const selectedTheme = pluckThemeFromContext(context);

41

const currentTheme = selectedTheme || defaultTheme;

42

43

// Apply theme-specific logic

44

document.documentElement.style.setProperty('--current-theme', currentTheme);

45

46

return storyFn();

47

};

48

};

49

```

50

51

### initializeThemeState

52

53

Registers themes with the addon state, enabling the theme switcher UI in the Storybook toolbar.

54

55

```typescript { .api }

56

/**

57

* Registers themes with the addon state for toolbar integration

58

* @param themeNames - Array of theme names to register

59

* @param defaultTheme - Name of the default theme

60

*/

61

function initializeThemeState(themeNames: string[], defaultTheme: string): void;

62

```

63

64

**Usage Example:**

65

66

```typescript

67

import { DecoratorHelpers } from '@storybook/addon-themes';

68

69

const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;

70

71

export const withCustomTheming = ({ themes, defaultTheme }) => {

72

// Register themes with the addon

73

initializeThemeState(Object.keys(themes), defaultTheme);

74

75

return (storyFn, context) => {

76

const selectedTheme = pluckThemeFromContext(context);

77

const theme = themes[selectedTheme] || themes[defaultTheme];

78

79

// Custom theming logic here

80

applyCustomTheme(theme);

81

82

return storyFn();

83

};

84

};

85

```

86

87

### useThemeParameters (Deprecated)

88

89

**⚠️ Deprecated**: This function is deprecated and will log a deprecation warning when used. Access theme parameters directly via the context instead.

90

91

```typescript { .api }

92

/**

93

* @deprecated Access parameters via context.parameters.themes instead

94

* Returns theme parameters for the current story

95

* @param context - Optional story context

96

* @returns Theme parameters object

97

*/

98

function useThemeParameters(context?: StoryContext): ThemesParameters;

99

```

100

101

**Modern Alternative:**

102

103

```typescript

104

// Instead of useThemeParameters()

105

const { themeOverride } = context.parameters.themes ?? {};

106

107

// Old deprecated way

108

const { themeOverride } = useThemeParameters(context);

109

```

110

111

## Custom Decorator Examples

112

113

### CSS Custom Properties Decorator

114

115

```typescript

116

import { DecoratorHelpers } from '@storybook/addon-themes';

117

118

const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;

119

120

export const withCSSCustomProperties = ({ themes, defaultTheme }) => {

121

initializeThemeState(Object.keys(themes), defaultTheme);

122

123

return (storyFn, context) => {

124

const selectedTheme = pluckThemeFromContext(context);

125

const { themeOverride } = context.parameters.themes ?? {};

126

127

const currentTheme = themeOverride || selectedTheme || defaultTheme;

128

const themeValues = themes[currentTheme];

129

130

// Apply CSS custom properties

131

const root = document.documentElement;

132

Object.entries(themeValues).forEach(([key, value]) => {

133

root.style.setProperty(`--theme-${key}`, String(value));

134

});

135

136

return storyFn();

137

};

138

};

139

140

// Usage

141

export const decorators = [

142

withCSSCustomProperties({

143

themes: {

144

light: { background: '#ffffff', text: '#000000' },

145

dark: { background: '#000000', text: '#ffffff' },

146

},

147

defaultTheme: 'light',

148

}),

149

];

150

```

151

152

### Vuetify Theme Decorator

153

154

```typescript

155

import { DecoratorHelpers } from '@storybook/addon-themes';

156

import { useTheme } from 'vuetify';

157

158

const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;

159

160

export const withVuetifyTheme = ({ themes, defaultTheme }) => {

161

initializeThemeState(Object.keys(themes), defaultTheme);

162

163

return (story, context) => {

164

const selectedTheme = pluckThemeFromContext(context);

165

const { themeOverride } = context.parameters.themes ?? {};

166

167

const selected = themeOverride || selectedTheme || defaultTheme;

168

169

return {

170

components: { story },

171

setup() {

172

const theme = useTheme();

173

theme.global.name.value = themes[selected];

174

return { theme };

175

},

176

template: `<v-app><story /></v-app>`,

177

};

178

};

179

};

180

181

// Usage in .storybook/preview.js

182

export const decorators = [

183

withVuetifyTheme({

184

themes: {

185

light: 'light',

186

dark: 'dark',

187

'high contrast': 'highContrast',

188

},

189

defaultTheme: 'light',

190

}),

191

];

192

```

193

194

### Body Class Decorator with Animation

195

196

```typescript

197

import { DecoratorHelpers } from '@storybook/addon-themes';

198

import { useEffect } from 'react';

199

200

const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;

201

202

export const withAnimatedBodyClass = ({ themes, defaultTheme, transitionDuration = 300 }) => {

203

initializeThemeState(Object.keys(themes), defaultTheme);

204

205

return (storyFn, context) => {

206

const selectedTheme = pluckThemeFromContext(context);

207

const { themeOverride } = context.parameters.themes ?? {};

208

209

useEffect(() => {

210

const currentTheme = themeOverride || selectedTheme || defaultTheme;

211

const body = document.body;

212

213

// Add transition

214

body.style.transition = `background-color ${transitionDuration}ms ease, color ${transitionDuration}ms ease`;

215

216

// Remove old theme classes

217

Object.values(themes).forEach(className => {

218

body.classList.remove(className as string);

219

});

220

221

// Add new theme class

222

body.classList.add(themes[currentTheme]);

223

224

return () => {

225

body.style.transition = '';

226

};

227

}, [themeOverride, selectedTheme]);

228

229

return storyFn();

230

};

231

};

232

```

233

234

### Multi-Context Theme Decorator

235

236

```typescript

237

import { DecoratorHelpers } from '@storybook/addon-themes';

238

239

const { initializeThemeState, pluckThemeFromContext } = DecoratorHelpers;

240

241

export const withMultiContextTheme = ({ themes, defaultTheme, contexts }) => {

242

initializeThemeState(Object.keys(themes), defaultTheme);

243

244

return (storyFn, context) => {

245

const selectedTheme = pluckThemeFromContext(context);

246

const { themeOverride } = context.parameters.themes ?? {};

247

248

const currentTheme = themeOverride || selectedTheme || defaultTheme;

249

const themeConfig = themes[currentTheme];

250

251

// Apply theme to multiple contexts

252

contexts.forEach(({ selector, property, value }) => {

253

const elements = document.querySelectorAll(selector);

254

elements.forEach(element => {

255

element.style[property] = themeConfig[value];

256

});

257

});

258

259

return storyFn();

260

};

261

};

262

263

// Usage

264

export const decorators = [

265

withMultiContextTheme({

266

themes: {

267

light: { bg: '#fff', text: '#000', accent: '#007bff' },

268

dark: { bg: '#000', text: '#fff', accent: '#66aaff' },

269

},

270

defaultTheme: 'light',

271

contexts: [

272

{ selector: 'body', property: 'backgroundColor', value: 'bg' },

273

{ selector: 'body', property: 'color', value: 'text' },

274

{ selector: '.accent', property: 'color', value: 'accent' },

275

],

276

}),

277

];

278

```

279

280

## Theme Override Support

281

282

All custom decorators should support theme overrides at the story level:

283

284

```typescript

285

export const MyStoryWithOverride = {

286

parameters: {

287

themes: {

288

themeOverride: 'dark' // Force this story to use dark theme

289

}

290

}

291

};

292

```

293

294

Access the override in your decorator:

295

296

```typescript

297

const { themeOverride } = context.parameters.themes ?? {};

298

const finalTheme = themeOverride || selectedTheme || defaultTheme;

299

```