Skip to content

Instantly share code, notes, and snippets.

Last active November 28, 2019 14:03
Show Gist options
  • Save tmeasday/4de20eab47226a870ab1025642ba848c to your computer and use it in GitHub Desktop.
Save tmeasday/4de20eab47226a870ab1025642ba848c to your computer and use it in GitHub Desktop.
Storybook example file format

Simple API: export Component (for docs) and examples ("renderables")

import React from 'react';

import Component from 'somewhere';

export default Component;

export const variant1 = () => <Component variant="1" />;
export const variant2 = () => <Component variant="2" />;
export const variant3 = () => <Component variant="3" />;

To use in storybook, simply add the examples file to your examples glob

config.examplesGlob = `**/*.examples.js`; // <- this is the default

Note this isn't a real thing yet, but we'll figure some way to avoid using require.context soon.

The above file will be added to your storybook as Component:variant1, Component:variant2 and Component:variant3. (You can have more complex names, see below).

Usage in other tools

A key benefit of a non-storybook specific file format is it becomes simple to reuse your examples in other tools, such as unit tests. For example:


import React from 'react';
import { mount } from 'enzyme';
import renderer from 'react-test-renderer';

import { variant1 } from './component.examples';

describe('variant1', () => {
  it('example renders correctly', () => {
    // Now you can do low-level unit tests on the example
    // Or you can do snapshot testing (although storyshots will still make this better)
    const component = renderer.create(variant1());
    let tree = component.toJSON();

Story and chapter parameters / decorators

To add metadata at the example level, add extra properties to the exported function. To do it to the component, export an object rather than the component class.

import React from 'react';
import Component from 'somewhere';

// Parameters or decorators for the component (i.e. all stories):
export default {
  component: Component,
  parameters: { viewports: [320, 1200] },
  decorators: [...],

// Parameters for a single story
export const variant1 = () => <Component variant="1" />;
variant1.parameters = { viewports: [320, 1200] };

We'll also make a super simple library to ensure you don't have to think about it:

import React from 'react';
import Component from 'somewhere';
import example from '@storybook/example';

export default example(Component, {
  parameters: { viewports: [320, 1200] },

export const variant1 = example(() => <Component variant="1" />, {
  parameters: { viewports: [320, 1200] },

FAQ / Rationale

  1. Why not return an object for examples?

We want examples to be consumed as simply as possible. Consider that developers will often be using them directly in tests etc. It is best if it can always be assume an example is a function returning something renderable.

OTOH, the component export (the default export) is more typically used by tooling (e.g. props tables for documentation) and so can be more flexible. Assigning properties on a component class (or equiv in other frameworks) seems dangerous and ugly.

  1. Why mutate the function object?

You don't have to. This works too:

export variant1 = Object.assign(() => <Component />, { parameters: { viewports: ... } });

Our feeling is the mutation syntax is simpler and easier to understand.


By default, the story "chapter" (or "kind") will take the title from the component's displayName (or equiv.), and the story's title will be the name of the example's export.

So the examples above will be called Component:variant1, Component:variant2 and Component:variant3.

We'll add a config flag that automatically prefixes the component's title with the pathname on disk.

Suppose you have:

config.prefixComponentTitlesByPathFrom = 'src/'

Then src/components/Button.examples.js will name its examples like components/Button:variant1, etc.

Alternatively, we'll support title and titlePrefix props on the component, and title on the example:

export default {
  component: Component,
  titlePrefix: 'components',
  // This would be equivalent to
  // title: 'components/Component',

// Parameters for a single story
export const variant1 = () => <Component variant="1" />;
variant1.title = 'Initial variant, I like to use wordy story names';

"props" addons

Addons that are more dynamic like actions and knobs have an opportunity to be refactored to better reflect the new format.


Actions are enabled by default (you can use the actions: { disabled: true } parameter to disable them, although there is no real benefit). To use:

export variant1 = ({ action }) => <Component onSomething={action('onSomething')} />;

We'll provide simple utilities that can be used for other tools:

import React from 'react';
import { mount } from 'enzyme';
import { jestAction } from '@storybook/addon-actions';

import { variant1 } from './component.examples';

it('calls the callback when you click it', () => {
  const action = jestAction();
  const wrapper = mount(variant1({ action }));



Knobs are similar, conceptually to actions:

export variant1 = Object.extend(
  ({ knobs: { name } }) => <Component name={name.get} onSetName={name.set} />, {
  knobs: {
    name: 'Default Name',

We'll provide simple utilities that can be used for other tools:

import React from 'react';
import { mount } from 'enzyme';
import { jestKnobs } from '@storybook/addon-knobs';

import { variant1 } from './component.examples';

it('calls the callback when you click it', () => {
  const knobs = jestKnobs(variant1);
  const wrapper = mount(variant1({ knobs }));

  const value = 'A different value';
Copy link

igor-dv commented Nov 23, 2018

  1. As I mentioned in Discord regarding the asyncness of the examples. What I mean is that when we handle these examples in the api implementation we should take into consideration that the story is an async function.
  2. We should make sure that this api is applied to every framework we are supporting.
  3. config.examplesGlob - does this depend on a single config (storybookjs/storybook#4169)?

Copy link

igor-dv commented Nov 23, 2018

BTW are you going to move this proposal to the SB issues?

Copy link

transitive-bullshit commented Nov 23, 2018

@tmeasday Excellent breakdown & looking forward to chatting more about this proposal synchronously since I think a lot of context is lost within discord + GH comments. With that being said, I wanted to state my initial feedback & thoughts here to get the conversation started.

First, for things I like about this proposal:

  • The goal. Encapsulating the data layer of Storybook to make it agnostic to the surrounding environment will ensure that Storybook remains a driving force within the wider community of Component development outside of just traditional Storybook users. Kudos 💯

  • Making the Component <-> Stories relationship and exports explicit. One constraint with SB at the moment that @sarahgp is currently investigating for our Docz compatibility layer is automated interop with Docz's PropsTable and Playground components. Much like their SB equivalents, they depend on docgen info attached to a target Component instance itself. Making the Story => Component and Component => Stories mappings explicit will make these types of use cases much easier to achieve and is what the user wants 95% of the time anyhow.

On the other hand, there are some things that I think warrant further discussion with the current version of the proposal:

  1. I don't like that the default export is the Component and not the stories. Since this is an example file format, semantically it makes sense for the main export to be the examples themselves, with the Component being a part of that mapping. Having the Component as the default export inverts this expectation which may work functionally but doesn't make sense to me semantically for what we're trying to achieve with this file convention.

  2. We should be very clear with the terminology / naming. What is a Story, and what is an Example in this parlance? imho we should just stick with Story since it's already the bread & butter of what SB provides, and this initiative is really all about encapsulating the bread from the butter.

  3. Using one named export per story results in two issues. A) the naming of stories is clunky, where the 95% use case requires you to add variant1.title = "my story title" and more importantly B) aggregating the list of stories in an examples.js file is now really awkward, since there's no way to automatically extract the list of stories for a given component / file. This is a very common and fundamental use case for this file format, and I don't think that arbitrarily named exports serves this purpose well at all.

  4. My original thinking was that if I import a Story from one of these example files, it would contain all the framework-specific knowledge necessary to render it in isolation, whether that be from an MDX file or from within Storybook itself. Having the story exports be simple "render" functions is nice & simple, but imho it fails to achieve this goal (which may or may not be a problem depending on your intended use case). This is one area where I think it would really help to talk things through over a call. Things like supporting Storybook decorators, addon functionality, and iframe or shadow-dom wrappers around Stories would not be possible to support with this proposal, whereas if we added a small, framework-specific wrapper around each Story export (see my WIP proposal below), then all of these considerations would be much easier to support. I know this is definitely an area with very clear tradeoffs, but I'm looking forward to talking them through with you 😄

Here's a super rough alternative that solves some of these issues as I've been thinking about this space over the past couple of days:

import { Story, Stories } from '@storybook/react'
import Button from './Button'

export default new Stories(Button, [
  new Story('with text', () => (
    <Button>Hello Button</Button>

  new Story('with emoji', () => (
      <span role='img' aria-label='so cool'>

Example mdx file:

import ButtonStories from './example.stories'

## Render one Button story

<ButtonStories id='with text' />

## Render all Button stories

<ButtonStories />

## Props

<PropsTable of={ButtonStories.component} />

A few things to note:

This proposal addresses each of my issues 1-4 above although it is slightly more verbose. The fact that this examples.js file imports from @storybook/react is not a problem imho because it's still making the format 100% encapsulated from the rest of Storybook. It's just adding a framework-specific compatibility layer where these exported stories could optionally be hooked into globally defined decorators, addon options, and/or custom story isolation strategies such as the current iframe isolation or a lighter-weight isolation such as shadow-dom (which is something we've experimented on over at

cc @ndelangen @mergebandit @sarahgp

Copy link

@igor-dv: yes I will move this to an issue, this format isn't the best for discussion. To your points:

  1. I think in theory the function can be async, although I'm not sure this is a common enough use case that we would address it in our documentation. I wouldn't encourage people to write test cases that await the story for instance "just in case" the story is async. That would be a pain.

  2. "We should make sure that this api is applied to every framework we are supporting." -- that's the plan I think. The function can return whatever the function returns right now in each framework. What that means in terms of how you use it in tests is up to the framework though.

  3. "config.examplesGlob - does this depend on a single config (storybookjs/storybook#4169)?" -- I was intentionally vague here because I don't want to get sidetracked on this question. I guess it probably does but I also suggested if we want to not get blocked on solving that we could add a commandline flag (say --entries) and use that until the single-file config is resolved.

Copy link

@transitive-bullshit -- thanks for your thoughts, and yes let's discuss soon. Again I will open an issue but quickly say a couple of things here:

  1. re: what is the default export -- I'm not really too hung up on this but i think @ndelangen has more opinions. Maybe let's document some more use cases and see what feels more natural.

  2. We kind of wanted to get away from the "story" naming for two reasons (a) this file format is supposed to be general, not tied to SB (b) the name "story" is a bit confusing for some audiences as it's a pretty overloaded term already (means one thing in the project management world, another thing in the design world, both of which sort of sound similar to an "example" but are actually quite different). Maybe this is a bad idea though.

  3. I think I disagree on this one a bit more (but still don't feel too strongly about it).
    (A) you might be surprised. I think more than 5% of stories are called things like "default", "noData" etc. Perhaps we should figure out what the conventional way to do it is. In Learn Storybook we used camelcased names but I don't think this was a particularly well considered decision.
    (B) can't you do import * as examples from 'Component.examples'?

  4. I think there is a clear tradeoff between this file format being more flexible vs more easily used. My concern is that I don't want the file format to be not usable in certain contexts because of the things imported. For instance as Jest is quite a different context to Webpack, importing a bunch of code to allow rendering a component inside storybook might be both unnecessary and not possible in a test. Also there is the question of whether you want all this SB stuff to be added to the story in your tests/wherever.

I think point 4 is the key question here (the rest is just polish). I can see why your suggested format works well for the use cases you are thinking about but I suspect it undermines a few more general things. It could be that I am overgeneralizing here and thinking of use cases that don't exist. Let's keep talking about it.

Copy link

Created an issue: storybookjs/storybook#4848

Copy link

jantimon commented Apr 17, 2019

Hey @tmeasday

I really like what you already create here and I would like to add my feedback from projects I worked on.

Glob (config.examplesGlob)

I would not add a custom module resolution algorithm for storybook (in terms of config.examplesGlob) but would rather try to keep it the way it is right now so we can keep all webpack module resolution and import features.

Glob usage (instead of require context)

If we don't use webpack require context but a glob pattern like you propose we will not be able to handle files created or deleted after the glob was executed.
So this will not update accordingly during development time.

Glob usage (instead of full freedom of customizable imports)

For many large scale projects we have lerna mono repositories which split for example small widgets like a small calculator or search component into its own package.
If we use a single glob instead of as many require calls as we do right now this introduces a lot of limitations in terms of the projects file structure. - Not forcing a folder structure on the user is one of the main advantages over other styleguide solutions.

Glob usage (node_modules inside mono repositories)

Lerna sub packages may have node_module with symbolic links which might be picked up by the glob.


I created a small proc which allows the following features:

  • Exporting random unrelated data or helper functions
  export const someData = { foo: "bar" };
  export const someFunction = () => { console.log("Hello Moon") };
  • Exporting Components
export const ReusableComponent = story.add("ReusableComponent", 
  () => (<div>Hello World</div>)
// Returns the same as
// export const ReusableComponent = () => (<div>Hello World</div>)
// without any mutation
  • Allow to create stories without exporting
story.add("Demo", () => <div>Hello World</div>);
  • Exporting multiple story contexts per file (maybe not that important)
const story = storyOf("MyStories|Components1");
story.add("Demo", () => <div>Hello World</div>);

const story2 = storyOf("MyStories|Components2");
story2.add("Demo", () => <div>Hello Moon</div>);

You can see the proof of concept here:

Parts which are not implemented

I have two further ideas which are not implemented:
Picking up the export name as title if no title is given - e.g.:

export const Component = story.add(() => <div>Hello World</div>);

Technically it would also be possible not to export the story wrapper.
So it would be almost the same api as with the current storybook implementation:

const story = storiesOf('Demo');
story.add('Component', () => <div>Component</div>);
story.add('Component2', () => <div>Component2</div>);

In combination of the other idea this might look like this:

const story = storiesOf('Demo');
export const Component = story.add(() => <div>Component</div>);
export const Component2 = story.add(() => <div>Component</div>);

After all this gives full ide support, typescript support, data reusability, compatibility to jest and similar test frameworks.


Maybe we don't need dramatic changes but have only drop the chain ability of stories to allow all we need.
What do you think?

Copy link

Thanks for your comments @jantimon, I appreciate them.

1. Re: globbing

Let's take this discussion elsewhere -- perhaps here: storybookjs/storybook#4169? Although it'll dovetail well with the new file format I think these are fairly orthogonal features and can probably be discussed separately.

2. Exporting "metadata" alongside stories from your file


export foo = {random: 'data'};

export story = /* some way of defining a story */

The issue here is that the consumer of this file needs to now distinguish between a story and a non-story when iterating through the exports. Is that a big problem? I am not sure.

3. Similar API

I'm not sure I see benefit in using storiesOf / component.add (I think you have component vs story mixed up in your examples) over a new API. I don't love the old API and it seems like an uncanny valley to have something that's "almost" the same but not actually the same.

4. Exporting vs not exporting stories.

I don't think we can have it both ways here. One design goal here was that the story file would not import a bunch of code that doesn't make sense in non-storybook contexts.

In your example, if you don't export the story, then it means you need a reference to the story store, with a bunch of associated junk that probably doesn't make any sense to have loaded when you want to run the story in Jest. This creates a huge surface area for incompatibility (now any change to the store potentially breaks the integration between stories and technology X). We are looking to get away from that.

That doesn't preclude a really simple wrapper like:

import { component, story } from '@storybook/stories'; // <- this is a super simple library for boxing story info

export Component = component(MyComponent, { metadata });

export storyOne = story(() => <Component variant="1" />, {
  parameters: { viewports: [320, 1200] },

This is of course another iteration of the @storybook/examples API we noodled with above, and similar to your idea in storybookjs/storybook#6538 (comment)

Copy link

jantimon commented Apr 18, 2019

Hey :) thanks for your quick response

My main goal is to keep the stories of storybook simple to learn, simple to maintain and simple to understand.
The second goal is to provide power users the ability to reuse components in tests and other stories.

1. Globing

I was a little bit afraid that this might cause a lot of work to achieve the same features that are here today - but it looks like you already thought about this topic a lot of #4169 so I am happy to exclude it here.

2. Exporting meta data and helper functions

Actually I would like it a lot if we could manage to keep the way javascript is written for stories is like every where else.
If we manage keeping the api very simple we won't need to change the entire javascript module system.
As a simple solution to your question - if a store is exported which is easy to detect then we can get its children in a very simple way.
Actually that is how the previous codesandbox works right now.

3. Similar API

In my opinion if users buy a new product from the same brand like an operation system, a phone or a car they want to reuse most of what they learned. So even if you don't love the api is a reason for the success of storybook. Invent an entire new system (like Angular1 to Angular2) can make people quite upset because they don't want to give up on the previous user experience and learnings.
Of cause right now some legacy decision block the way forward so this part will be a challenging balance act.

However I totally agree that if the api is similar but different people will copy over the wrong examples from stackoverflow and will have a hard time to find out what is wrong.

4. Exporting

I agree that a thin api without sub dependencies is a great idea.
But I believe a store can help a lot here if it is done right and probably without any harm to jest.
The following store could easily be executed by jest as long as we don't introduce life cycle hooks:

export function createStoryGroup(title) {
  const stories = Object.create({
    ["[storybookData]"]: { title },
    create: (storyTitle, storyFn) => {
      return (stories[storyTitle] = storyFn);
  return stories;

A story file might look a little bit like the following demo:

// I don't like this name - please don't take it to serious
import {createStoryGroup} from 'storybook';

// Create a tiny store with a name to allow grouping stories and to attach metadata
// This solution would allow multiple stores in the same file with any name that suits the user
export const storyGroup = createStoryGroup('UI|Charts/PieCharts');

// Add it to `storyGroup` with its issue id or other information
// No export needed because of the storyGroup store
// This might be very useful because almost every storybook user is right now
// using storybook although it does not provide an export 
storyGroup.create('Blue version [#123]', () => <div>a lot of markup of the story</div>);

// Add it to `storyGroup` and export it as `Green`
// This allows reusing the component in tests or other storybook files 
export const Green = storyGroup.create('Blue version [#123]', () => <div>a lot of markup of the story</div>);

// Same as green just different order
// Here we define the component first and set the title after it
// Not sure if this makes sense as placing the title last is the 
// opposite of how storybook or any unit test framework works
export const Yellow = () => <div>a lot of markup of the story</div>;
storyGroup.create('Yellow version [#123]', Yellow);

// Use the export name as title:
export const Orange = storyGroup.create(() => <div>a lot of markup of the story</div>);

// The api is just a first draft and you might also have a better idea
// but just for completeness : 
storyGroup.add('A title', () => <div>a lot of markup of the story</div>)
storyGroup('A title', () => <div>a lot of markup of the story</div>)
addStory(storyGroup, 'A title', () => <div>a lot of markup of the story</div>)

The names createStoryGroup or .create can probably be improved a lot but maybe you get the idea.

The exported store can be picked up by the core storybook javascript code just like a "database".
As will know all exported names from the file and all stories from that "database" it can even reuse the exported name like in the Orange example.

I hope you are not to scarred about the store idea and maybe you can outline where you see problems for such a tiny store.

Copy link

Let's talk more about this store idea as I am not sure I really understand what the benefit you see in having the store embedded inside the story file is.

Is it purely backwards compatibility / familiarity?

We will definitely support the old storiesOf syntax for some time after releasing this new format; possibly several major releases/years.

As for familiarity I am not sure that there is a huge difference between

// something like you've suggested
export const Blue = storyGroup.create('Blue version [#123]', () => <div>a lot of markup of the story</div>);

// or the simpler
export const Blue = () => <div>a lot of markup of the story</div>;

// or even with a wrapper as mentioned above
export const Blue = story('Blue version [#123]', () => <div>a lot of markup of the story</div>);

Is there some other benefit of having the story boxed up with the component that I am not seeing?

Copy link

jantimon commented May 10, 2019

The store has a minor advantage and the main reason why I would like to see it in future versions:

  1. (the minor) - You can create multiple section in the same file:
export const storyGroup1 = createStoryGroup('UI|Charts/PieCharts');
// add some stories
export const storyGroup2 = createStoryGroup('UI|Charts/BarCharts');
// add some stories

However I don't believe that this is a very common case

  1. (the main reason) It allows us to identify very easily if the export is a story:
// Simplified pseudo storybook core code
import * as userStory from './a-user-story';

const exportedStoryPropertyNames = Object.keys(userStory);
const allStories = [];

// Iterate over all exports of a story file:
exportedStoryPropertyNames.forEach((exportedStoryPropertyName) => {
   // We identify if the exported property is a store 
   // (e.g. by checking for an attribute like `[storybookData]` or using instance of)
   const isStore = userStory[exportedStoryPropertyName] instanceOf StoryGroup;
   if (isStore) {
     // We can copy over all stories from the stores

// Optional step: Now that we have all stories and the export names we can combine the data
// This will allow us to use the title `Orange` from the following code: 
// export const Orange = storyGroup.create(() => <div>a lot of markup of the story</div>);

So the main advantage of a store is that it allows the user to export stories and unrelated data side by side.
He could export random data, components, stories for unit tests without confusing storybook.
Without a store it is hard to find out if export const Blue = () => <div>a lot of markup of the story</div>; is a story or a util function.
It also allows a title to include spaces or other characters which are not valid export names.

Here is a very basic proof of concept how that could work (based of the code above):


Unit test:

Copy link

Hey @jantimon, we ended up solving the problem you mentioned with a white/blacklist on which exports are stories in the component metadata (includeStories/excludeStories):

Copy link

jantimon commented Nov 27, 2019

hey @tmeasday

I really like the idea how you solved it there as it keeps the main goal (writing stories) simple and edge cases like includeStories and excludeStories are possible too. 👍

What you might consider is to add an optional util to help understanding the api of the the default export:

     import {defineStories} from 'storybook';

     export default defineStories({{
        title: 'Path|To/MyComponent',
        component: MyComponent,

The code for defineStories could be: defineStories(config) => config and therefore it would be totally optional.

  • Add jsdoc information to explain future maintainers / developers what is going on here:


  • Add typings to the defineStories helper to have auto complete on properties


  • Add typings to the defineStories helper to prevent typos in title


What do you think?

Copy link

Interesting idea! What do you think @shilman?

Copy link

shilman commented Nov 28, 2019

@tmeasday @jantimon I love it. Can we call it meta to make it consistent with the MDX Meta doc block?

Copy link

export default meta({ looks good to me 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment