Docs
Storybook Docs

Stories for multiple components

It's useful to write stories that render two or more components at once if those components are designed to work together. For example, ButtonGroup, List, and Page components.

Subcomponents

When the components you're documenting have a parent-child relationship, you can use the subcomponents property to document them together. This is especially useful when the child component is not meant to be used on its own, but only as part of the parent component.

Here's an example with List and ListItem components:

List.stories.js|jsx
This snippet doesn't exist for null. In the meantime, here's the React snippet.
import React from 'react';
 
import { List } from './List';
import { ListItem } from './ListItem';
 
export default {
  component: List,
  subcomponents: { ListItem }, //👈 Adds the ListItem component as a subcomponent
};
 
export const Empty = {};
 
export const OneItem = {
  render: (args) => (
    <List {...args}>
      <ListItem />
    </List>
  ),
};

Note that by adding a subcomponents property to the default export, we get an extra panel on the ArgTypes and Controls tables, listing the props of ListItem:

Subcomponents in ArgTypes doc block

Subcomponents are only intended for documentation purposes and have some limitations:

  1. The argTypes of subcomponents are inferred (for the renderers that support that feature) and cannot be manually defined or overridden.
  2. The table for each documented subcomponent does not include controls to change the value of the props, because controls always apply to the main component's args.

Let's talk about some techniques you can use to mitigate the above, which are especially useful in more complicated situations.

Reusing story definitions

We can also reduce repetition in our stories by reusing story definitions. Here, we can reuse the ListItem stories' args in the story for List:

List.stories.js|jsx
This snippet doesn't exist for null. In the meantime, here's the React snippet.
import React from 'react';
 
import { List } from './List';
import { ListItem } from './ListItem';
 
//👇 We're importing the necessary stories from ListItem
import { Selected, Unselected } from './ListItem.stories';
 
export default {
  component: List,
};
 
export const ManyItems = {
  render: (args) => (
    <List {...args}>
      <ListItem {...Selected.args} />
      <ListItem {...Unselected.args} />
      <ListItem {...Unselected.args} />
    </List>
  ),
};

By rendering the Unchecked story with its args, we are able to reuse the input data from the ListItem stories in the List.

Creating a Template Component

Another option that is more “data”-based is to create a special “story-generating” template component:

List.stories.js|jsx
This snippet doesn't exist for null. In the meantime, here's the React snippet.
import { List } from './List';
import { ListItem } from './ListItem';
 
//👇 Imports a specific story from ListItem stories
import { Unchecked } from './ListItem.stories';
 
export default {
  /* 👇 The title prop is optional.
   * See https://storybook.js.org/docs/configure/#configure-story-loading
   * to learn how to generate automatic titles
   */
  title: 'List',
  component: List,
};
 
//👇 The ListTemplate construct will be spread to the existing stories.
const ListTemplate = {
  render: ({ items, ...args }) => {
    return (
      <List>
        {items.map((item) => (
          <ListItem {...item} />
        ))}
      </List>
    );
  },
};
 
export const Empty = {
  ...ListTemplate,
  args: {
    items: [],
  },
};
 
export const OneItem = {
  ...ListTemplate,
  args: {
    items: [
      {
        ...Unchecked.args,
      },
    ],
  },
};

This approach is a little more complex to setup, but it means you can more easily reuse the args to each story in a composite component. It also means that you can alter the args to the component with the Controls addon.