0
# Portable Stories
1
2
Functionality for using Storybook stories outside of the Storybook environment, particularly useful for unit testing, component documentation, and integration testing. Portable stories allow you to reuse your story definitions in Jest, Vitest, Playwright, and other testing frameworks.
3
4
## Type Dependencies
5
6
The portable stories API depends on core Storybook types:
7
8
```typescript
9
// Main portable stories exports
10
import { setProjectAnnotations, composeStory, composeStories } from "@storybook/react";
11
12
// Core Storybook internal types
13
import type {
14
Args,
15
NamedOrDefaultProjectAnnotations,
16
NormalizedProjectAnnotations,
17
ProjectAnnotations,
18
StoryAnnotationsOrFn,
19
Store_CSFExports,
20
StoriesWithPartialProps,
21
ComposedStoryFn
22
} from "storybook/internal/types";
23
24
// React renderer type (see React Renderer Types documentation)
25
import type { ReactRenderer } from "./react-renderer.md#react-renderer-interface";
26
27
// Meta type (see Story Types & Metadata documentation)
28
import type { Meta } from "./story-types.md#meta-type";
29
```
30
31
## Capabilities
32
33
### Set Project Annotations
34
35
Configures global project settings for story composition, typically used in test setup files.
36
37
```typescript { .api }
38
/**
39
* Sets the global configuration for story composition.
40
* Should be called once in your test setup to apply global decorators, parameters, and other configurations.
41
*
42
* @param projectAnnotations - Global project configuration (e.g., from .storybook/preview.ts)
43
* @returns Normalized project annotations for the React renderer
44
*/
45
function setProjectAnnotations(
46
projectAnnotations:
47
| NamedOrDefaultProjectAnnotations<any>
48
| NamedOrDefaultProjectAnnotations<any>[]
49
): NormalizedProjectAnnotations<ReactRenderer>;
50
```
51
52
**Usage Example:**
53
54
```typescript
55
// setup-tests.ts
56
import { setProjectAnnotations } from "@storybook/react";
57
import * as projectAnnotations from "../.storybook/preview";
58
59
// Apply global configuration to all composed stories
60
setProjectAnnotations(projectAnnotations);
61
```
62
63
### Compose Story
64
65
Creates a composed story component from a story definition and meta, ready for use in testing or other contexts.
66
67
```typescript { .api }
68
/**
69
* Composes a single story with its metadata and configuration into a React component.
70
* The resulting component can be rendered directly in tests or other environments.
71
*
72
* @param story - Story definition (object or function)
73
* @param componentAnnotations - Component metadata (Meta export)
74
* @param projectAnnotations - Optional project configuration (if not set globally)
75
* @param exportsName - Optional name for the story (for debugging)
76
* @returns Composed story component ready for rendering
77
*/
78
function composeStory<TArgs extends Args = Args>(
79
story: StoryAnnotationsOrFn<ReactRenderer, TArgs>,
80
componentAnnotations: Meta<TArgs | any>,
81
projectAnnotations?: ProjectAnnotations<ReactRenderer>,
82
exportsName?: string
83
): ComposedStoryFn<ReactRenderer, Partial<TArgs>>;
84
```
85
86
**Usage Example:**
87
88
```typescript
89
// Button.test.tsx
90
import { render, screen } from "@testing-library/react";
91
import { composeStory } from "@storybook/react";
92
import Meta, { Primary, Secondary } from "./Button.stories";
93
94
// Compose individual stories
95
const PrimaryButton = composeStory(Primary, Meta);
96
const SecondaryButton = composeStory(Secondary, Meta);
97
98
test("renders primary button", () => {
99
render(<PrimaryButton />);
100
expect(screen.getByRole("button")).toHaveClass("primary");
101
});
102
103
test("renders secondary button with custom props", () => {
104
render(<SecondaryButton label="Custom Label" />);
105
expect(screen.getByText("Custom Label")).toBeInTheDocument();
106
});
107
108
// Test with overridden args
109
test("renders button with specific size", () => {
110
render(<PrimaryButton size="large" />);
111
expect(screen.getByRole("button")).toHaveClass("large");
112
});
113
```
114
115
### Compose Stories
116
117
Creates composed components for all stories in a story file, providing an object with all stories ready for testing.
118
119
```typescript { .api }
120
/**
121
* Composes all stories from a story file into an object of React components.
122
* Each story becomes a component that can be rendered independently.
123
*
124
* @param csfExports - All exports from a .stories file (import * as stories)
125
* @param projectAnnotations - Optional project configuration (if not set globally)
126
* @returns Object containing all composed stories, excluding CSF metadata
127
*/
128
function composeStories<TModule extends Store_CSFExports<ReactRenderer, any>>(
129
csfExports: TModule,
130
projectAnnotations?: ProjectAnnotations<ReactRenderer>
131
): Omit<StoriesWithPartialProps<ReactRenderer, TModule>, keyof Store_CSFExports>;
132
```
133
134
**Usage Example:**
135
136
```typescript
137
// Button.test.tsx
138
import { render, screen } from "@testing-library/react";
139
import { composeStories } from "@storybook/react";
140
import * as stories from "./Button.stories";
141
142
// Compose all stories from the file
143
const { Primary, Secondary, Large, Small } = composeStories(stories);
144
145
describe("Button Stories", () => {
146
test("Primary story renders correctly", () => {
147
render(<Primary />);
148
expect(screen.getByRole("button")).toHaveClass("primary");
149
});
150
151
test("Secondary story renders correctly", () => {
152
render(<Secondary />);
153
expect(screen.getByRole("button")).toHaveClass("secondary");
154
});
155
156
// Test all stories programmatically
157
Object.entries(composeStories(stories)).forEach(([name, Story]) => {
158
test(`${name} story renders without errors`, () => {
159
render(<Story />);
160
expect(screen.getByRole("button")).toBeInTheDocument();
161
});
162
});
163
});
164
```
165
166
### Advanced Testing Patterns
167
168
**Testing with React Testing Library:**
169
170
```typescript
171
import { render, screen, fireEvent } from "@testing-library/react";
172
import { composeStory } from "@storybook/react";
173
import Meta, { Interactive } from "./Button.stories";
174
175
const InteractiveButton = composeStory(Interactive, Meta);
176
177
test("button handles click events", async () => {
178
const mockFn = vi.fn();
179
render(<InteractiveButton onClick={mockFn} />);
180
181
fireEvent.click(screen.getByRole("button"));
182
expect(mockFn).toHaveBeenCalledTimes(1);
183
});
184
```
185
186
**Testing with Custom Context:**
187
188
```typescript
189
import { render } from "@testing-library/react";
190
import { composeStory } from "@storybook/react";
191
import { ThemeProvider } from "./ThemeProvider";
192
import Meta, { Themed } from "./Button.stories";
193
194
const ThemedButton = composeStory(Themed, Meta);
195
196
test("button renders with custom theme", () => {
197
render(
198
<ThemeProvider theme="dark">
199
<ThemedButton />
200
</ThemeProvider>
201
);
202
// Test themed button behavior
203
});
204
```
205
206
**Playwright Integration:**
207
208
```typescript
209
import { test, expect } from "@playwright/test";
210
import { composeStories } from "@storybook/react";
211
import * as stories from "./Button.stories";
212
213
const { Primary } = composeStories(stories);
214
215
test("visual regression test", async ({ page }) => {
216
// Mount the composed story in Playwright
217
await page.goto("/test-page");
218
await page.evaluate(() => {
219
// Render the composed story
220
const root = ReactDOM.createRoot(document.getElementById("root"));
221
root.render(React.createElement(Primary));
222
});
223
224
await expect(page).toHaveScreenshot("primary-button.png");
225
});
226
```
227
228
## Internal Default Annotations
229
230
Default project configuration used internally by the React renderer.
231
232
```typescript { .api }
233
/**
234
* Internal default project annotations for React renderer.
235
* Includes React-specific rendering configuration and compatibility layers.
236
*/
237
const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations<ReactRenderer>;
238
```
239
240
This constant is used internally and typically doesn't need to be accessed directly, but it provides the base React rendering configuration that can be extended with custom project annotations.
241
242
## Type Definitions
243
244
### Composed Story Function Type
245
246
```typescript { .api }
247
/**
248
* Type for composed story functions that can be rendered as React components.
249
* Supports partial args and maintains type safety.
250
*/
251
type ComposedStoryFn<TRenderer extends Renderer, TArgs> = React.ComponentType<Partial<TArgs>> & {
252
args: TArgs;
253
argTypes: ArgTypes;
254
parameters: Parameters;
255
storyName: string;
256
};
257
```
258
259
### Story Annotations Type
260
261
```typescript { .api }
262
/**
263
* Type for story definitions that can be functions or objects.
264
* Used by composeStory for flexible story input.
265
*/
266
type StoryAnnotationsOrFn<TRenderer extends Renderer, TArgs> =
267
| StoryAnnotations<TRenderer, TArgs>
268
| AnnotatedStoryFn<TRenderer, TArgs>;
269
```