0
# Story Composition
1
2
Story composition functionality for creating and testing stories outside of the Storybook environment. This is essential for unit testing, integration testing, and component validation workflows.
3
4
## Capabilities
5
6
### Compose Single Story
7
8
Creates a composed story that can be rendered and tested independently.
9
10
```typescript { .api }
11
/**
12
* Compose a single story for independent testing and rendering
13
* @param story - The story function to compose
14
* @param meta - The meta object containing story metadata
15
* @param projectAnnotations - Optional project-level annotations
16
* @returns A composed story function with additional metadata
17
*/
18
function composeStory<TRenderer, TArgs>(
19
story: Story<TRenderer, TArgs>,
20
meta: Meta<TRenderer, TArgs>,
21
projectAnnotations?: ProjectAnnotations<TRenderer>
22
): ComposedStory<TRenderer, TArgs>;
23
24
interface ComposedStory<TRenderer = unknown, TArgs = unknown> {
25
(args?: Partial<TArgs>): unknown;
26
id: string;
27
storyName: string;
28
args: TArgs;
29
parameters: Parameters;
30
argTypes: ArgTypes<TArgs>;
31
play?: PlayFunction<TRenderer, TArgs>;
32
}
33
```
34
35
**Usage Example:**
36
37
```typescript
38
import { composeStory } from "storybook/preview-api";
39
import { render, screen } from "@testing-library/react";
40
import { Button } from "./Button";
41
import type { Meta, StoryObj } from "@storybook/react";
42
43
const meta: Meta<typeof Button> = {
44
title: "Example/Button",
45
component: Button,
46
parameters: {
47
layout: "centered",
48
},
49
argTypes: {
50
backgroundColor: { control: "color" },
51
},
52
};
53
54
const Primary: StoryObj<typeof meta> = {
55
args: {
56
primary: true,
57
label: "Button",
58
},
59
};
60
61
// Compose the story for testing
62
const ComposedPrimary = composeStory(Primary, meta);
63
64
// Use in tests
65
test("renders primary button", () => {
66
render(<ComposedPrimary />);
67
const button = screen.getByRole("button");
68
expect(button).toHaveClass("storybook-button--primary");
69
});
70
```
71
72
### Compose All Stories
73
74
Composes all stories from a stories module for bulk testing operations.
75
76
```typescript { .api }
77
/**
78
* Compose all stories from a stories module
79
* @param module - The stories module containing meta and stories
80
* @param projectAnnotations - Optional project-level annotations
81
* @returns Object containing all composed stories indexed by story ID
82
*/
83
function composeStories<TModule extends StoriesModule>(
84
module: TModule,
85
projectAnnotations?: ProjectAnnotations<TRenderer>
86
): ComposedStoryModule<TModule>;
87
88
type ComposedStoryModule<TModule extends StoriesModule> = {
89
[K in keyof Omit<TModule, 'default'>]: TModule[K] extends Story<infer TRenderer, infer TArgs>
90
? ComposedStory<TRenderer, TArgs>
91
: never;
92
};
93
94
interface StoriesModule {
95
default: Meta;
96
[key: string]: Story | Meta;
97
}
98
```
99
100
**Usage Example:**
101
102
```typescript
103
import { composeStories } from "storybook/preview-api";
104
import * as stories from "./Button.stories";
105
106
const { Primary, Secondary, Large, Small } = composeStories(stories);
107
108
describe("Button stories", () => {
109
test("Primary story renders correctly", () => {
110
render(<Primary />);
111
expect(screen.getByRole("button")).toBeInTheDocument();
112
});
113
114
test("Secondary story renders correctly", () => {
115
render(<Secondary />);
116
expect(screen.getByRole("button")).toHaveClass("storybook-button--secondary");
117
});
118
});
119
```
120
121
### Set Project Annotations
122
123
Configures project-level annotations that apply to all composed stories.
124
125
```typescript { .api }
126
/**
127
* Set project-level annotations for all composed stories
128
* @param annotations - Project-wide configuration or array of configurations to merge
129
*/
130
function setProjectAnnotations<TRenderer>(
131
annotations: ProjectAnnotations<TRenderer> | ProjectAnnotations<TRenderer>[]
132
): void;
133
134
interface ProjectAnnotations<TRenderer = unknown> {
135
parameters?: Parameters;
136
decorators?: DecoratorFunction<TRenderer>[];
137
args?: Args;
138
argTypes?: ArgTypes;
139
globals?: Args;
140
globalTypes?: GlobalTypes;
141
}
142
```
143
144
**Usage Example:**
145
146
```typescript
147
import { setProjectAnnotations } from "storybook/preview-api";
148
149
// Set up global configuration
150
setProjectAnnotations({
151
parameters: {
152
backgrounds: {
153
default: "light",
154
values: [
155
{ name: "light", value: "#ffffff" },
156
{ name: "dark", value: "#333333" },
157
],
158
},
159
actions: { argTypesRegex: "^on[A-Z].*" },
160
},
161
decorators: [
162
(Story) => (
163
<div style={{ margin: "3em" }}>
164
<Story />
165
</div>
166
),
167
],
168
globals: {
169
backgrounds: { value: "light" },
170
},
171
});
172
```
173
174
## Hook API for Stories
175
176
Storybook provides React-like hooks that can be used within stories and decorators for state management and side effects.
177
178
### Story Context Hooks
179
180
```typescript { .api }
181
/**
182
* Access story arguments with update capability
183
* @returns Tuple of current args, update function, and reset function
184
*/
185
function useArgs<TArgs>(): [
186
TArgs,
187
(newArgs: Partial<TArgs>) => void,
188
(argNames?: (keyof TArgs)[]) => void
189
];
190
191
/**
192
* Access global parameters with update capability
193
* @returns Tuple of current globals and update function
194
*/
195
function useGlobals(): [Args, (newGlobals: Args) => void];
196
197
/**
198
* Access a specific story parameter
199
* @param parameterKey - The parameter key to retrieve
200
* @param defaultValue - Default value if parameter is undefined
201
* @returns The parameter value or undefined
202
*/
203
function useParameter<S>(parameterKey: string, defaultValue?: S): S | undefined;
204
205
/**
206
* Access the complete story context
207
* @returns The current story context object
208
*/
209
function useStoryContext<TRenderer>(): StoryContext<TRenderer>;
210
```
211
212
### Standard React-style Hooks
213
214
```typescript { .api }
215
function useState<S>(initialState: S | (() => S)): [S, (update: S | ((prevState: S) => S)) => void];
216
function useEffect(create: () => (() => void) | void, deps?: any[]): void;
217
function useReducer<S, A>(reducer: (state: S, action: A) => S, initialState: S): [S, (action: A) => void];
218
function useMemo<T>(nextCreate: () => T, deps?: any[]): T;
219
function useCallback<T>(callback: T, deps?: any[]): T;
220
function useRef<T>(initialValue: T): { current: T };
221
```
222
223
**Usage Example:**
224
225
```typescript
226
import { useArgs, useEffect } from "storybook/preview-api";
227
228
export const InteractiveButton: Story = {
229
render: (args) => {
230
const [{ count }, updateArgs] = useArgs();
231
232
useEffect(() => {
233
console.log("Button count changed:", count);
234
}, [count]);
235
236
return (
237
<button
238
onClick={() => updateArgs({ count: (count || 0) + 1 })}
239
{...args}
240
>
241
Clicked {count || 0} times
242
</button>
243
);
244
},
245
args: {
246
count: 0,
247
},
248
};
249
```
250
251
## Core Types
252
253
```typescript { .api }
254
interface Story<TRenderer = unknown, TArgs = unknown> {
255
(args: TArgs, context: StoryContext<TRenderer>): unknown;
256
storyName?: string;
257
parameters?: Parameters;
258
args?: Partial<TArgs>;
259
argTypes?: ArgTypes<TArgs>;
260
decorators?: DecoratorFunction<TRenderer, TArgs>[];
261
play?: PlayFunction<TRenderer, TArgs>;
262
}
263
264
interface Meta<TRenderer = unknown, TArgs = unknown> {
265
title?: string;
266
component?: unknown;
267
subcomponents?: Record<string, unknown>;
268
parameters?: Parameters;
269
args?: Partial<TArgs>;
270
argTypes?: ArgTypes<TArgs>;
271
decorators?: DecoratorFunction<TRenderer, TArgs>[];
272
loaders?: LoaderFunction<TRenderer, TArgs>[];
273
}
274
275
interface StoryContext<TRenderer = unknown> {
276
id: string;
277
name: string;
278
title: string;
279
parameters: Parameters;
280
args: Args;
281
argTypes: ArgTypes;
282
globals: Args;
283
viewMode: ViewMode;
284
loaded: Record<string, unknown>;
285
abortSignal: AbortSignal;
286
}
287
288
type DecoratorFunction<TRenderer = unknown, TArgs = unknown> = (
289
story: () => unknown,
290
context: StoryContext<TRenderer>
291
) => unknown;
292
293
type PlayFunction<TRenderer = unknown, TArgs = unknown> = (
294
context: StoryContext<TRenderer> & { canvasElement: HTMLElement }
295
) => Promise<void> | void;
296
```