Component Story Format (CSF)
The CSF 4 API is composed of functions to help you write stories and the annotations used to configure them. Note how three of the functions operate as factories, each producing the next function in the chain (definePreview โ preview.meta โ meta.story), providing full type safety at each step.
This API reference only documents the latest version of the Component Story Format, CSF 4. Earlier versions are documented in the Storybook 10 docs.
defineMain
Type: (config: StorybookConfig) => StorybookConfig
The main Storybook config is specified by the defineMain function. This function is type-safe and will automatically infer types for your project.
// Replace your-framework with the framework you are using (e.g., nextjs-vite, sveltekit, etc.)
import { defineMain } from '@storybook/your-framework/node';
export default defineMain({
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: ['@storybook/addon-a11y'],
});definePreview
Type: (annotations: Annotations) => Preview
The definePreview function specifies your project's story configuration. This function is also type-safe and will infer types throughout your project.
It accepts an object containing the annotations to apply to all stories in the project.
addons is a particularly important annotation to specify. Any addon you include here will have their types available throughout your project, enabling autocompletion and type checking.
The function returns a preview object, which contains the meta function used to define story metadata.
// Replace your-framework with the framework you are using (e.g., nextjs-vite, sveltekit, etc.)
import { definePreview } from '@storybook/your-framework';
import addonA11y from '@storybook/addon-a11y';
export default definePreview({
// ๐ Add your addons here
addons: [addonA11y()],
parameters: {
// type-safe!
a11y: {
options: { xpath: true },
},
},
});The preview configuration will be automatically updated to reference the necessary addons when installing an addon via npx storybook add <addon-name> or running storybook dev.
Which addons should be specified in preview?
The ability for an addon to provide annotation types (parameters, globals, etc.) is new and not all addons support it yet.
If an addon provides annotations (i.e. it distributes a ./preview export), it can be imported in two ways:
-
For official Storybook addons, you import the default export:
import addonName from '@storybook/addon-name' -
For community addons, you should import the entire module and access the addon from there:
import * as addonName from 'community-addon-name'
preview.meta
Type: (annotations: Annotations) => Meta
The meta function on the preview object is used to define the metadata for your stories. It accepts an object containing the component, title, and other annotations.
The component field is used to render your component (unless a custom render function is provided). It is also used for automatic prop table generation and display of other component metadata.
The title field is optional and should be unique (i.e., not re-used across files). If you don't specify one, it can be inferred from the story's path on disk. For more information, see the section on configuring story loading.
The function returns a meta object, which contains the story function used to define stories.
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
parameters: {
// type-safe!
layout: 'centered',
},
});preview.type.meta
By default, preview.meta will infer the type of your component's props automatically. However, if you need to extend or modify the inferred types, you can use the preview.type function to specify custom types. The props of the component are still inferred, so the type only has to hold the args that you add.
import preview from '../.storybook/preview';
import { Button } from './Button';
import { Icon } from './Icon';
type CustomArgs = { icon: 'check' | 'heart' | 'star' };
const meta = preview.type<{ args: CustomArgs }>().meta({
component: Button,
// ๐ Correct types
render: ({ children, icon, ...args }) => (
<Button {...args}>
<Icon name={icon} /> {children}
</Button>
),
});
export const Primary = meta.story({
// ๐ Correct types
args: {
children: 'Button',
icon: 'star',
},
});Every key in the args of preview.meta must be an arg of your stories: a prop of the component, an arg of its typed render function, an arg used by one of its decorators, or an arg declared with preview.type. The values are checked against those types, so literal values like variant: 'primary' need no as const.
For more information on typing your stories, refer to the writing stories in TypeScript guide.
meta.story
Type: (annotations: Annotations) => Story
Finally, the story function on the meta object defines the stories. This function accepts an object containing the args, parameters, and other story annotations.
// ...from above
const meta = preview.meta({
//...
});
export const Primary = meta.story({
args: {
primary: true,
},
});meta.type.story
Stories take their types from the meta. When a single story needs something the other stories of the file don't have, such as an extra arg read by its render function , you can use the meta.type function to add it for that story only.
import preview from '../.storybook/preview';
import { Button } from './Button';
import { Icon } from './Icon';
type CustomArgs = { icon: 'check' | 'heart' | 'star' };
const meta = preview.meta({
component: Button,
});
export const Primary = meta.story({
args: { children: 'Button' },
});
export const WithIcon = meta.type<{ args: CustomArgs }>().story({
// ๐ `icon` is typed, and required, in this story only
args: { children: 'Button', icon: 'star' },
render: ({ children, icon, ...args }) => (
<Button {...args}>
<Icon name={icon} /> {children}
</Button>
),
});meta.type accepts the same type argument as preview.type, and the two can be combined.
An arg you add must be set in that story, unless you declare it as optional (meta.type<{ args: { icon?: string } }>()). Args that the meta already sets stay optional, except the ones you redeclare in meta.type: the story must set those again, because the meta's value was not checked against the new type.
<Story>.extend
You can use the .extend method to create a new story based on an existing one, with the option to override or add new properties.
// ...from above
const meta = preview.meta({
//...
});
export const Primary = meta.story({
args: {
primary: true,
},
});
export const PrimaryDisabled = Primary.extend({
args: {
disabled: true,
},
});Property merging details
Properties are merged intelligently:
argsare shallow mergedparametersare deep merged, except for arrays, which are replaceddecoratorsandtagsare concatenated
// ...from above
const meta = preview.meta({
//...
});
export const Example = meta.story({
args: {
primary: true,
exampleArray: ['a', 'b'],
exampleObject: { a: 'a', b: 'b' },
},
parameters: {
exampleArray: ['a', 'b'],
exampleObject: { a: 'a', b: 'b' },
},
tags: ['a'],
});
/*
* ๐ Final values applied:
* {
* args: {
* primary: true,
* disabled: true,
* exampleArray: ['c'],
* exampleObject: { a: 'c' }
* },
* parameters: {
* exampleArray: ['c'],
* exampleObject: { a: 'c', b: 'b' }
* },
* tags: ['a', 'b']
* }
*/
export const ExtendedExample = Example.extend({
args: {
disabled: true,
exampleArray: ['c'],
exampleObject: { a: 'c' },
},
parameters: {
disabled: true,
exampleArray: ['c'],
exampleObject: { a: 'c' },
},
tags: ['b'],
});<Story>.test
(โ ๏ธ Experimental)
A more ergonomic way to define tests for your stories. While this API is still experimental, it is documented in the RFC to gather feedback and must be enabled via the experimentalTestSyntax feature flag.
// ...from above
import { expect } from '@storybook/test';
export const PrimaryDisabled = Primary.extend({ args: { disabled: true } });
// ๐ .test method: Attach tests to a story
// The test function can run the same code as the play function
PrimaryDisabled.test('should be disabled', async ({ canvas, userEvent, args }) => {
const button = await canvas.findByRole('button');
await userEvent.click(button);
await expect(button).toHaveAttribute('aria-disabled', 'true');
await expect(args.onClick).not.toHaveBeenCalled();
});Annotations
Annotations are metadata to configure and control the behavior of stories. Unless otherwise specified, they can be defined at multiple levels:
- Preview: Applies to all stories in your project.
- Meta: Applies to all stories within a single file.
- Story: Applies to an individual story.
Annotation definitions cascade, meaning that story-level annotations override meta-level annotations, and meta-level annotations override preview-level annotations. Some annotations have specific cascading behavior that may differ from this general rule:
argsare shallow mergedparametersare deep merged, except for arrays, which are replaceddecoratorsandtagsare concatenated
addons
Type: Array<PreviewAddonEntry>
Valid within: Preview
Only supported in CSF 4
Addons included here will have their types made available to your project.
See definePreview for more details.
afterEach
Type:
| (context: StoryContext) => void
| Array<(context: StoryContext) => void>Valid within: Preview, Meta, Story
Runs after the play function, for final assertions. Can be async. One function or an array of them.
Do not use this for cleanup operations. Instead, perform any necessary cleanup in the return function of beforeAll or beforeEach. This ensures that cleanup happens when it should: before the next story renders instead of immediately after the current story has finished rendering.
import preview from '../.storybook/preview';
import { Page } from './Page';
const meta = preview.meta({
component: Page,
// ๐ Runs after each story in this file
async afterEach(context) {
console.log(`โ
Tested ${context.name} story`);
},
});
export const Basic = meta.story({
async play({ canvas }) {
// ...
},
});args
Type: { [argName: string]: any }
Valid within: Preview, Meta, Story
Values passed to the component as props or attributes.
See the Args guide for more details.
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
});
export const Primary = meta.story({
args: {
primary: true,
label: 'Button',
},
});argTypes
Type: { [argName: string]: InputType }
Valid within: Preview, Meta, Story
Metadata for each arg, such as its control, description, and options. Storybook infers most of it from the component.
See the ArgTypes reference for more details.
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
argTypes: {
// ๐ All Button stories expect a label arg
label: {
control: 'text',
description: 'Overwritten description',
},
},
});beforeAll
Type: () => (void | (() => void))
Valid within: Preview
Runs once before any story loads. Can be async. Can return a cleanup function, which can also by async. When rendering stories in Storybook, the cleanup function is called before beforeAll is called again (i.e. after you make a change to preview.ts). When rendering stories in Vitest, the cleanup function is called after all tests in a test file have finished running.
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { definePreview } from '@storybook/your-framework';
import { init } from '../project-bootstrap';
export default definePreview({
async beforeAll() {
await init();
},
});beforeEach
Type:
| (context: StoryContext) => (void | (() => void))
| Array<(context: StoryContext) => (void | (() => void))>Valid within: Preview, Meta, Story
Runs before the story renders. Can be async. One function or an array of them. Can return a cleanup function, which can also be async and runs when Storybook tears down the story, just before the next story (or the same story) starts rendering.
import MockDate from 'mockdate';
import preview from '../.storybook/preview';
import { Page } from './Page';
const meta = preview.meta({
component: Page,
// ๐ Set the value of Date for every story in the file
async beforeEach() {
MockDate.set('2024-02-14');
// ๐ Reset the Date after each story
return () => {
MockDate.reset();
};
},
});
export const Basic = meta.story({
async play({ canvas }) {
// ... This will run with the mocked Date
},
});component
Type: Depends on the renderer being used (e.g., React.ComponentType<any> for React).
Valid within: Meta
The component that the stories document. Storybook uses it to infer argTypes and to render stories by default.
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
});
export const Primary = meta.story({
args: {
primary: true,
label: 'Button',
},
});decorators
Type: Depends on the renderer (e.g., Array<(Story: () => JSX.Element, context: StoryContext) => JSX.Element> for React)
Valid within: Preview, Meta, Story
Functions that wrap the story in extra markup or context. One function or an array of them. Story decorators wrap the story first, then meta decorators, then preview decorators.
See the Decorators guide for more details.
import preview from '../.storybook/preview';
import { YourComponent } from './YourComponent';
const meta = preview.meta({
component: YourComponent,
decorators: [
(Story) => (
<div style={{ margin: '3em' }}>
{/* ๐ Decorators in Storybook also accept a function. Replace <Story/> with Story() to enable it */}
<Story />
</div>
),
],
});excludeStories
Type: string[] | RegExp
Valid within: Meta
Exports will be checked against this array or regular expression, and any matching exports will not be treated as stories.
See Non-story exports for more details.
import preview from '../.storybook/preview';
import { MyComponent } from './MyComponent';
import someData from './data.json';
const meta = preview.meta({
component: MyComponent,
includeStories: ['SimpleStory', 'ComplexStory'], // ๐ Storybook loads these stories
excludeStories: /.*Data$/, // ๐ Storybook ignores anything that contains Data
});
export const simpleData = { foo: 1, bar: 'baz' };
export const complexData = { foo: 1, foobar: { bar: 'baz', baz: someData } };
export const SimpleStory = meta.story({
args: {
data: simpleData,
},
});
export const ComplexStory = meta.story({
args: {
data: complexData,
},
});globals
Type: { [globalName: string]: Value }
Sets global values for these stories and locks them in the toolbar. The exact type of Value depends on the definition of the globalType you are setting.
See the Toolbars & globals guide for more details.
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
globals: {
// ๐ Set background value for all component stories
backgrounds: { value: 'gray', grid: false },
},
});
export const OnDark = meta.story({
globals: {
// ๐ Override background value for this story
backgrounds: { value: 'dark' },
},
});globalTypes
Type: { [globalName: string]: ToolbarType }
Valid within: Preview
Defines globals and their toolbar menus.
See the Toolbars & globals guide for more details.
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { definePreview } from '@storybook/your-framework';
export default definePreview({
globalTypes: {
theme: {
description: 'Global theme for components',
toolbar: {
// The label to show for this toolbar item
title: 'Theme',
icon: 'circlehollow',
// Array of plain string values or MenuItem shape (see below)
items: ['light', 'dark'],
// Change title based on selected value (recommended for consistency with the Storybook UI)
dynamicTitle: true,
},
},
},
initialGlobals: {
theme: 'light',
},
});id
Type: string
Valid within: Meta
The ID used in story URLs. Must be statically analyzable. Generally not used. Set it to keep URLs the same after you rename a title.
includeStories
Type: string[] | RegExp
Valid within: Meta
Exports will be checked against this array or regular expression, and only matching exports will be treated as stories.
See Non-story exports for more details.
import preview from '../.storybook/preview';
import { MyComponent } from './MyComponent';
import someData from './data.json';
const meta = preview.meta({
component: MyComponent,
includeStories: ['SimpleStory', 'ComplexStory'], // ๐ Storybook loads these stories
excludeStories: /.*Data$/, // ๐ Storybook ignores anything that contains Data
});
export const simpleData = { foo: 1, bar: 'baz' };
export const complexData = { foo: 1, foobar: { bar: 'baz', baz: someData } };
export const SimpleStory = meta.story({
args: {
data: simpleData,
},
});
export const ComplexStory = meta.story({
args: {
data: complexData,
},
});initialGlobals
Type: { [globalName: string]: { [key: string]: string } }
Valid within: Preview
Starting values for globals, such as theme or locale.
See the Toolbars & globals guide for more details.
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { definePreview } from '@storybook/your-framework';
export default definePreview({
globalTypes: {
theme: {
description: 'Global theme for components',
toolbar: {
// The label to show for this toolbar item
title: 'Theme',
icon: 'circlehollow',
// Array of plain string values or MenuItem shape (see below)
items: ['light', 'dark'],
// Change title based on selected value (recommended for consistency with the Storybook UI)
dynamicTitle: true,
},
},
},
initialGlobals: {
theme: 'light',
},
});loaders
Type: (context: StoryContext) => { [key: string]: any } | void
Valid within: Preview, Meta, Story
Functions that load data before the story renders. Can be async. The results are merged into context.loaded.
See the Loaders guide for more details.
import preview from '../.storybook/preview';
import { TodoItem } from './TodoItem';
/*
*๐ Render functions are a framework specific feature to allow you control on how the component renders.
* See https://storybook.js.org/docs/writing-stories/csf
* to learn how to use render functions.
*/
const meta = preview.meta({
component: TodoItem,
render: (args, { loaded: { todo } }) => <TodoItem {...args} {...todo} />,
});
export const Primary = meta.story({
loaders: [
async () => ({
todo: await (await fetch('https://jsonplaceholder.typicode.com/todos/1')).json(),
}),
],
});mount
Type: Depends on the renderer (e.g., (context: StoryContext) => (ui?: JSX.Element) => Promise<Canvas> for React)
Valid within: Preview, Meta, Story
Replaces the function that the play function gets as mount. The Canvas it resolves to has Testing Library queries for the story's root element. Most projects do not need it.
name
Type: string
Valid within: Story
The story's name in the sidebar. If you do not set it, Storybook makes it from the export name.
import preview from '../.storybook/preview';
import { MyComponent } from './MyComponent';
const meta = preview.meta({
component: MyComponent,
});
export const Simple = meta.story({
name: 'So simple!',
// ...
});parameters
Type: { [name: string]: any }
Valid within: Preview, Meta, Story
Static metadata, usually for addons and features. Storybook deep-merges it from preview to meta to story.
See the Parameters guide for more details.
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
//๐ Creates specific parameters at the component level
parameters: {
backgrounds: {
options: {},
},
},
});play
Type: (context: StoryContext & { canvas: TestingLibraryCanvas; userEvent: TestingLibraryUserEvent; context: StoryContext }) => void
Runs after the story renders, to interact with it and make assertions.
See the Play function guide for more details.
import preview from '../.storybook/preview';
import { MyComponent } from './MyComponent';
const meta = preview.meta({
component: MyComponent,
});
export const ExampleStory = meta.story({
play: async ({ canvas, userEvent }) => {
// Starts querying from the component's root element
await userEvent.type(canvas.getByTestId('example-element'), 'something');
await userEvent.click(canvas.getByRole('button'));
},
});render
Type: Depends on the renderer (e.g., (args: { [argName: string]: any }, context: StoryContext) => JSX.Element for React)
Valid within: Preview, Meta, Story
Sets how the story renders from its args. If you do not set it, Storybook renders the component with the args.
import preview from '../.storybook/preview';
import { Layout } from './Layout';
import { MyComponent } from './MyComponent';
const meta = preview.meta({
component: MyComponent,
});
// This story uses a render function to fully control how the component renders.
export const Example = meta.story({
render: () => (
<Layout>
<header>
<h1>Example</h1>
</header>
<article>
<MyComponent />
</article>
</Layout>
),
});subcomponents
Type: Depends on the renderer (e.g., { [name: string]: React.ComponentType<any> } for React)
Valid within: Meta
Related components to show in the Controls table, each in its own tab.
See the Stories for multiple components guide for more details.
import * as React from 'react';
import preview from '../.storybook/preview';
import { List } from './List';
import { ListItem } from './ListItem';
const meta = preview.meta({
component: List,
subcomponents: { ListItem }, //๐ Adds the ListItem component as a subcomponent
});
export const Empty = meta.story();
export const OneItem = meta.story({
render: (args) => (
<List {...args}>
<ListItem />
</List>
),
});tags
Type: string[]
Valid within: Preview, Meta, Story
Apply built-in and custom tags to stories, for example to indicate their status or categorize them. A ! prefix removes a tag that a higher level added.
See the Tags guide for more details.
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
/*
* All stories in this file will have these tags applied:
* - autodocs
* - dev (implicit default, inherited from preview)
* - test (implicit default, inherited from preview)
*/
tags: ['autodocs'],
});
export const ExperimentalFeatureStory = meta.story({
/*
* This particular story will have these tags applied:
* - experimental
* - autodocs (inherited from meta)
* - dev (inherited from meta)
* - test (inherited from meta)
*/
tags: ['experimental'],
});title
Type: string
Valid within: Meta
Controls where the component appears in the sidebar. Separate levels with /. Must be statically analyzable.
If you do not set it, Storybook infers it from the file path of the story file.
import preview from '../.storybook/preview';
import { MyComponent } from './MyComponent';
const meta = preview.meta({
/* ๐ The title prop is optional.
* See https://storybook.js.org/docs/configure/#configure-story-loading
* to learn how to generate automatic titles
*/
component: MyComponent,
title: 'components/MyComponent/MyComponent',
});
export const Basic = meta.story({
args: {
something: 'Something else',
},
});