Writing Stories

A Storybook is a collection of stories. Each story represents a single visual state of a component.

Technically, a story is a function that returns something that can be rendered to screen.

Basic story

Here is a simple example of stories for a Button component:

  1. import React from 'react';
  2. import { action } from '@storybook/addon-actions';
  3. import Button from './Button';
  4. export default {
  5. component: Button,
  6. title: 'Button',
  7. };
  8. export const text = () => <Button onClick={action('clicked')}>Hello Button</Button>;
  9. export const emoji = () => (
  10. <Button onClick={action('clicked')}>
  11. <span role="img" aria-label="so cool">
  12. 😀 😎 👍 💯
  13. </span>
  14. </Button>
  15. );

This is what you’ll see in Storybook:

Basic stories

The named exports define the Button’s stories, and the default export defines metadata that applies to the group. In this case, the component is Button. The title determines the title of the group in Storybook’s left-hand navigation panel and should be unique, i.e. not re-used across files. In this case it’s located at the top level, but typically it’s positioned within the story hierarchy.

This example is written in Storybook’s Component Story Format (CSF). Storybook also supports:

  • a classic storiesOf API, which adds stories through Storybook’s API.
  • an experimental MDX syntax, which mixes longform Markdown docs and JSX stories.Since CSF is a new addition to Storybook, most Storybook examples you’ll find in the wild are written to the storiesOf API.

Furthermore, Storybook for React Native currently only supports the storiesOf format. React Native will get CSF and MDX support in a future release.

Story file location

Stories are easier to maintain when they are located alongside the components they are documented. We recommend:

  1. └── src
  2. └── components
  3. └── button
  4. ├── button.js
  5. └── button.stories.js

It’s up to you to find a naming/placing scheme that works for your project/team. Other naming conventions:stories sub-directory in component directory

  1. └── src
  2. └── components
  3. └── button
  4. ├── button.js
  5. └── stories
  6. └── button.stories.js

stories directory outside src directory

  1. ├── src
  2. └── components
  3. └── button.js
  4. └── stories
  5. └── button.stories.js

Loading stories

Stories are loaded in the .storybook/config.js file.

The most convenient way to load stories is by filename. For example, if you stories files are located in the src/components directory, you can use the following snippet:

  1. import { configure } from '@storybook/react';
  2. configure(require.context('../src/components', true, /\.stories\.js$/), module);

NOTE: The configure function should be called only once in config.js.

The configure function accepts:

  • A single require.contextreq
  • An array of reqs to load from multiple locations
  • A loader function that should return void or an array of module exportsIf you want to load from multiple locations, you can use an array:
  1. import { configure } from '@storybook/react';
  2. configure([
  3. require.context('../src/components', true, /\.stories\.js$/)
  4. require.context('../lib', true, /\.stories\.js$/)
  5. ], module);

Or if you want to do some custom loading logic, you can use a loader function. Just remember to return an array of module exports if you want to use Component Story Format. Here’s an example that forces files to load in a specific order.

  1. import { configure } from '@storybook/react';
  2. const loaderFn = () => ([
  3. require('./welcome.stories.js'),
  4. require('./prelude.stories.js'),
  5. require('./button.stories.js'),
  6. require('./input.stories.js'),
  7. ]);
  8. configure(loaderFn, module);

Here’s another example that mixes manual loading with glob-style loading:

  1. import { configure } from '@storybook/react';
  2. const loaderFn = () => {
  3. const allExports = [require('./welcome.stories.js')];
  4. const req = require.context('../src/components', true, /\.stories\.js$/);
  5. req.keys().forEach(fname => allExports.push(req(fname)));
  6. return allExports;
  7. };
  8. configure(loaderFn, module);

Storybook uses Webpack’s require.context to load modules dynamically. Take a look at the relevant Webpack docs to learn more about how to use require.context.

If you are using the storiesOf API directly, or are using @storybook/react-native where CSF is unavailable, you should use a loader function with no return value:

  1. import { configure } from '@storybook/react';
  2. const loaderFn = () => {
  3. // manual loading
  4. require('./welcome.stories.js');
  5. require('./button.stories.js');
  6. // dynamic loading, unavailable in react-native
  7. const req = require.context('../src/components', true, /\.stories\.js$/);
  8. req.keys().forEach(req(fname));
  9. };
  10. configure(loaderFn, module);

Furthermore, the React Native packager resolves all imports at build-time, so it’s not possible to load modules dynamically. There is a third party loader react-native-storybook-loader to automatically generate the import statements for all stories.

Decorators

A decorator is a way to wrap a story with a common set of components, for example if you want to wrap a story in some formatting, or provide some context to the story.

Decorators can be applied globally, at the component level, or individually at the story level. Global decorators are typically applied in the Storybook config files, and component/story decorators are applied in the story file.

Here is an example of a global decorator which centers every story in the storybook:

  1. import React from 'react';
  2. import { load, addDecorator } from '@storybook/react';
  3. addDecorator(storyFn => <div style={{ textAlign: 'center' }}>{storyFn()}</div>);
  4. load(require.context('../src/components', true, /\.stories\.js$/), module);

And here’s an example of component/local decorators. The component decorator wraps all the stories in a yellow frame, and the story director wraps a single story in an additional red frame.

  1. import React from 'react';
  2. import MyComponent from './MyComponent';
  3. export default {
  4. title: 'MyComponent',
  5. decorators: [storyFn => <div style={{ backgroundColor: 'yellow' }}>{storyFn()}</div>],
  6. };
  7. export const normal = () => <MyComponent />;
  8. export const special = () => <MyComponent text="The Boss" />;
  9. special.story = {
  10. decorators: [storyFn => <div style={{ border: '5px solid red' }}>{storyFn()}</div>],
  11. };

Decorators are not just for story formatting, they are generally useful for any kind of context needed by a story.

  • Theming libraries require a theme to be passed in through context. Rather than redefining this in every story, just add a decorator.
  • Likewise, state management libraries like Redux provide a global data store through context.
  • Finally, Storybook addons heavily use decorators. For example, the Storybook’s Knobs addon uses decorators to modify the input properties of the story based on a UI.

Parameters

Parameters are custom metadata for a story. Like decorators, they can also be hierarchically applied: globally, at the component level, or locally at the story level.

Here’s an example where we are annotating our stories with Markdown notes using parameters, to be displayed in the Notes addon.

We first apply some notes globally in the Storybook config.

  1. import { load, addParameters } from '@storybook/react';
  2. import defaultNotes from './instructions.md';
  3. addParameters({ notes: defaultNotes });

This would make sense if, for example, instructions.md contained instructions on how to document your components when there is no documentation available.

Then for components that did have documentation, we might override it at the component/story level:

  1. import React from 'react';
  2. import MyComponent from './MyComponent';
  3. import componentNotes from './notes.md';
  4. import specialNotes from '/.special.md';
  5. export default {
  6. title: 'MyComponent',
  7. parameters: { notes: componentNotes },
  8. };
  9. export const small = () => <MyComponent text="small" />;
  10. export const medium = () => <MyComponent text="medium" />;
  11. export const special = () => <MyComponent text="The Boss" />;
  12. special.story = {
  13. parameters: { notes: specialNotes },
  14. };

In this example, the small and medium stories get the component notes documented in notes.md (as opposed to the generic instructions in instructions.md). The special story gets some special notes.

Searching

By default, search results will show up based on the file name of your stories. As of storybook 5, you can extend this with notes to have certain stories show up when the search input contains matches. For example, if you built a Callout component that you want to be found by searching for popover or tooltip as well, you could use notes like this:

  1. export const callout = () => <Callout>Some children</Callout>;
  2. callout.story = {
  3. parameters: { notes: 'popover tooltip' },
  4. };

Story hierarchy

Stories can be organized in a nested structure using ”/” as a separator, and can be given a top-level heading using a ”|” root separator.

For example the following snippets nest the Button and Checkbox components within the Atoms group, under a top-level heading called Design System.

  1. // Button.stories.js
  2. import React from 'react';
  3. import Button from './Button';
  4. export default {
  5. title: 'Design System|Atoms/Button',
  6. };
  7. export const normal = () => <Button onClick={action('clicked')}>Hello Button</Button>;
  1. // Checkbox.stories.js
  2. import React from 'react';
  3. import Checkbox from './Checkbox';
  4. export default {
  5. title: 'Design System|Atoms/Checkbox',
  6. };
  7. export const empty = () => <Checkbox label="empty" />;
  8. export const checked = () => <Checkbox label="checked" checked />;

If you prefer other characters as separators, you can configure this using the hierarchySeparator and hierarchyRootSeparator config options. See theconfiguration options parameter page to learn more.

Generating nesting path based on __dirname

Nesting paths can be programmatically generated with template literals because story names are strings.

One example would be to use base from paths.macro:

  1. import React from 'react';
  2. import base from 'paths.macro';
  3. import BaseButton from '../components/BaseButton';
  4. export default {
  5. title: `Other|${base}/Dirname Example`,
  6. };
  7. export const story1 = () => <BaseButton label="Story 1" />;
  8. export const story2 = () => <BaseButton label="Story 2" />;

This uses babel-plugin-macros.

Run multiple storybooks

Multiple storybooks can be built for different kinds of stories or components in a single repository by specifying different port numbers in the start scripts:

  1. {
  2. "scripts": {
  3. "start-storybook-for-theme": "start-storybook -p 9001 -c .storybook-theme",
  4. "start-storybook-for-app": "start-storybook -p 8001 -c .storybook-app"
  5. }
  6. }