Docs
Storybook Docs

Component Story Format (CSF)

Component Story Format (CSF) is the recommended way to write stories. It's an open standard based on ES6 modules that is portable beyond Storybook.

If you are writing stories in the older storiesOf() syntax, you can find documentation in an advanced README.

In CSF, stories and component metadata are defined as ES Modules. Every component story file consists of a required default export and one or more named exports.

CSF is supported in all frameworks except React Native, where you should use the storiesOf API instead.

Default export

The default export defines metadata about your component, including the component itself, its title (where it will show up in the navigation UI story hierarchy), decorators, and parameters.

The component field is required and used by addons 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).

MyComponent.story.js|jsx|ts|tsx
import { MyComponent } from './MyComponent';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Path/To/MyComponent',
  component: MyComponent,
  decorators: [ ... ],
  parameters: { ... }
}

For more examples, see writing stories.

Named story exports

With CSF, every named export in the file represents a story object by default.

MyComponent.story.ts|tsx
import React from 'react';
 
import { ComponentStory, ComponentMeta } from '@storybook/react';
 
import { MyComponent } from './MyComponent';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Path/To/MyComponent',
  component: MyComponent,
} as ComponentMeta<typeof MyComponent>;
 
export const Basic: ComponentStory<typeof MyComponent> = () => <MyComponent/>;
 
export const WithProp: ComponentStory<typeof MyComponent> = () => <MyComponent prop="value"/>;

The exported identifiers will be converted to "start case" using Lodash's startCase function. For example:

IdentifierTransformation
nameName
someNameSome Name
someNAMESome NAME
some_custom_NAMESome Custom NAME
someName1234Some Name 1 2 3 4

We recommend that all export names to start with a capital letter.

Story objects can be annotated with a few different fields to define story-level decorators and parameters, and also to define the name of the story.

Storybook's name configuration element is helpful in specific circumstances. Common use cases are names with special characters or Javascript restricted words. If not specified, Storybook defaults to the named export.

MyComponent.story.js|jsx|ts|tsx
import { MyComponent } from './MyComponent';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Path/To/MyComponent', 
  component: MyComponent,
};
 
export const Simple = () => ({
  //๐Ÿ‘‡ Your story goes here
});
Simple.decorators = [ ... ];
Simple.parameters = { ... };
Simple.storyName = 'So simple!';

Args story inputs

Starting in SB 6.0, stories accept named inputs called Args. Args are dynamic data that are provided (and possibly updated by) Storybook and its addons.

Consider Storybookโ€™s "Button" example of a text button that logs its click events:

Button.stories.ts|tsx
import React from 'react';
 
import { ComponentStory, ComponentMeta } from '@storybook/react';
 
import { action } from '@storybook/addon-actions';
 
import { Button } from './Button';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Button', 
  component: Button,
} as ComponentMeta<typeof Button>;
 
export const Basic: ComponentStory<typeof Button> = () => <Button label="Hello" onClick={action('clicked')} />;

Now consider the same example, re-written with args:

Button.stories.js|jsx|ts|tsx
import React from 'react';
 
import { action } from '@storybook/addon-actions';
 
import { Button } from './Button';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Button',
  component: Button,
};
 
export const Text = ({ label, onClick }) => <Button label={label} onClick={onClick} />;
 
Text.args = {
  label: 'Hello',
  onClick: action('clicked'),
};

Or even more simply:

Button.stories.js|jsx|ts|tsx
import { Button } from './Button';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Button',
  component: Button,
}
 
export const Text = (args) => <Button {...args} />;

Not only are these versions shorter and more accessible to write than their no-args counterparts, but they are also more portable since the code doesn't depend on the actions addon specifically.

For more information on setting up Docs and Actions, see their respective documentation.

Play function

Storybook's play functions are small snippets of code executed when the story renders in the UI. They are convenient helper methods to help you test use cases that otherwise weren't possible or required user intervention.

A good use case for the play function is a form component. With previous Storybook versions, you'd write your set of stories and had to interact with the component to validate it. With Storybook's play functions, you could write the following story:

LoginForm.stories.ts|tsx
import React from 'react';
 
import { ComponentStory, ComponentMeta } from '@storybook/react';
 
import { within, userEvent } from '@storybook/testing-library';
 
import { expect } from '@storybook/jest';
 
import { LoginForm } from './LoginForm';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Form',
  component: LoginForm,
} as ComponentMeta<typeof LoginForm>;
 
const Template: ComponentStory<typeof LoginForm> = (args) => <LoginForm {...args} />;
 
export const EmptyForm = Template.bind({});
 
/*
* See https://storybook.js.org/docs/6/writing-stories/play-function#working-with-the-canvas
* to learn more about using the canvasElement to query the DOM
*/
export const FilledForm = Template.bind({});
FilledForm.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement);
 
  // ๐Ÿ‘‡ Simulate interactions with the component
  await userEvent.type(canvas.getByTestId('email'), 'email@provider.com');
  
  await userEvent.type(canvas.getByTestId('password'), 'a-random-password');
 
  // See https://storybook.js.org/docs/6/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel
  await userEvent.click(canvas.getByRole('button'));
 
  // ๐Ÿ‘‡ Assert DOM structure
  await expect(
    canvas.getByText(
      'Everything is perfect. Your account is ready and we should probably get you started!'
    )
  ).toBeInTheDocument();
};

When the story renders in the UI, Storybook executes each step defined in the play function and runs the assertions without the need for user interaction.

Storybook export vs. name handling

Storybook handles named exports and the name option slightly differently. When should you use one vs. the other?

Storybook will always use the named export to determine the story ID and URL.

If you specify the name option, it will be used as the story display name in the UI. Otherwise, it defaults to the named export, processed through Storybook's storyNameFromExport and lodash.startCase functions.

MyComponent-test.js
it('should format CSF exports with sensible defaults', () => {
  const testCases = {
    name: 'Name',
    someName: 'Some Name',
    someNAME: 'Some NAME',
    some_custom_NAME: 'Some Custom NAME',
    someName1234: 'Some Name 1234',
    someName1_2_3_4: 'Some Name 1 2 3 4',
  };
  Object.entries(testCases).forEach(([key, val]) => {
    expect(storyNameFromExport(key)).toBe(val);
  });
});

When you want to change the name of your story, rename the CSF export. It will change the name of the story and also change the story's ID and URL.

It would be best if you used the name configuration element in the following cases:

  1. You want the name to show up in the Storybook UI in a way that's not possible with a named export, e.g., reserved keywords like "default", special characters like emoji, spacing/capitalization other than what's provided by storyNameFromExport.
  2. You want to preserve the Story ID independently from changing how it's displayed. Having stable Story IDs is helpful for integration with third-party tools.

Non-story exports

In some cases, you may want to export a mixture of stories and non-stories (e.g., mocked data).

You can use the optional configuration fields includeStories and excludeStories in the default export to make this possible. You can define them as an array of strings or regular expressions.

Consider the following story file:

MyComponent.stories.ts|tsx
import React from 'react';
 
import { ComponentStory, ComponentMeta } from '@storybook/react';
 
import { MyComponent } from './MyComponent';
 
import someData from './data.json';
 
export default {
  /* ๐Ÿ‘‡ The title prop is optional.
  * See https://storybook.js.org/docs/6/configure#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'MyComponent', 
  component: MyComponent,
  includeStories: ['SimpleStory', 'ComplexStory'], // ๐Ÿ‘ˆ Storybook loads these stories
  excludeStories: /.*Data$/, // ๐Ÿ‘ˆ Storybook ignores anything that contains Data
} as ComponentMeta<typeof MyComponent>;
 
export const simpleData = { foo: 1, bar: 'baz' };
export const complexData = { foo: 1, foobar: { bar: 'baz', baz: someData } };
 
const Template: ComponentStory<typeof MyComponent> = (args) => <MyComponent {...args} />;
 
export const SimpleStory = Template.bind({});
SimpleStory.args = {
  data: simpleData,
};
 
export const ComplexStory = Template.bind({});
ComplexStory.args = {
  data: complexData,
};

When this file renders in Storybook, it treats ComplexStory and SimpleStory as stories and ignores the data named exports.

For this particular example, you could achieve the same result in different ways, depending on what's convenient:

  • includeStories: /^[A-Z]/
  • includeStories: /.*Story$/
  • includeStories: ['SimpleStory', 'ComplexStory']
  • excludeStories: /^[a-z]/
  • excludeStories: /.*Data$/
  • excludeStories: ['simpleData', 'complexData']

The first option is the recommended solution if you follow the best practice of starting story exports with an uppercase letter (i.e., use UpperCamelCase).