Component Story Format

Source file extensions: .stories.(js|jsx|ts|tsx)

Stories are expressed in Storybook’s Component Story Format (CSF).

WebComponents.dev supports Storybook 6.x improvements.

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.

Stories

Stories are named exports returning a function.

export const BasicButton = () => story;

The rendering of story depends on his type.

String

export const BasicButton = () => '<button>Basic</button>';

HTMLElement

var element = document.createElement('button');
element.appendChild(document.createTextNode('Click Me!'));
export const BasicButton = () => element;

Or any of the following frameworks

lit-html
import { html } from 'lit-html';
import './my-button.js';

const name = 'World';
export const BasicButton = () => html`<button>Hello ${name}</button>`;
FAST
import { html } from '@microsoft/fast-element';
import './my-button.js';

const name = 'World';
export const BasicButton = () => html`<button>Hello ${name}</button>`;
uhtml
import { html } from 'uhtml';
import './my-button.js';

const name = 'World';
export const BasicButton = () => html`<button>Hello ${name}</button>`;
lighterhtml
import { html } from 'lighterhtml';
import './my-button.js';

const name = 'World';
export const BasicButton = () => html`<button>Hello ${name}</button>`;
Omi (JSX)
/** @jsx h */
import './index.js';
import { h } from 'omi';

export const story1 = () => <my-counter></my-counter>;
Stencil (JSX)
/* @jsx h */
import { h } from '@stencil/core';
import './my-counter.tsx';

export const story1 = () => <my-counter></my-counter>;
Preact (JSX)
/** @jsx h */
import { h } from 'preact';
import MyCounter from './index.js';

export const story1 = () => <MyCounter></MyCounter>;
React (JSX)
import React from 'react';
import MyCounter from './index.js';

export const story1 = () => <MyCounter></MyCounter>;
Riot
import Counter from './index.riot';

export const story1 = () => Counter;

export const story2 = () => ({
  Component: Counter,
  props: {
    count: 5,
  },
});
Sinuous
import { html } from 'sinuous';
import MyCounter from './index.js';

export const story1 = () => html`<div><${MyCounter}></${MyCounter}></div>`;
Solid (JSX)
import MyCounter from './index.js';

export const story1 = () => MyCounter;
export const jsx = () => () => <MyCounter />; // Double function
Svelte
import Counter from './index.svelte';

export const story1 = () => Counter;

export const story2 = () => ({
  Component: Counter,
  props: {
    count: 5,
  },
});
Vue 3
import MyCounter from './index.vue';

export const story1 = () => ({
  components: { MyCounter },
  template: '<my-counter></my-counter>',
});

Stories are rendered using our open-source universal-story-renderer.

You can also configure a global preview module that will apply to all stories

Parameters

Supported parameters are:

parameters: {
  backgrounds: {
    default: "light" | "dark",
  },
  layout: "centered" | "padded" | "fullscreen",
}

You can default the parameters of all stories in export default

export default {
  parameters: {
    backgrounds: {
      default: 'dark',
    },
    layout: 'centered',
  },
};

Or setup for a specific story

export const BasicButton = () => html`<button>Hello ${name}</button>`;
BasicButton.parameters = {
  backgrounds: {
    default: 'dark',
  },
  layout: 'centered',
};

Args

Args allows stories to receive dynamic data as input arguments.

const Template = (args) => <Button {...args} />;

export const Text = Template.bind({});
Text.args = { label: 'hello', background: '#ff0' };

export const Emoji = Template.bind({});
Emoji.args = { ...Text.args, label: '😀 😎 👍 💯' };

For more details see Introducing Storybook Args.

Component Story Format 3.0

CSF 3.0 and improves on developer experience.

Story objects

CSF 3.0 now relies on stories being defined as objects, simplifying how to compose them. For example, this is how you previously had to write a new story based on a previous one:

export const PrimaryOnDark = Primary.bind({});
PrimaryOnDark.args = Primary.args;
PrimaryOnDark.parameters = { background: { default: 'dark' } };

While you are now able to use the spread operator to copy that previous story definition:

export const PrimaryOnDark = {
  ...Primary,
  parameters: { background: { default: 'dark' } },
};

Default render functions

In CSF 3.0, story rendering is defined through render functions. You would typically define a local default render function like this:

export default {
  render: (args) => <Button {...args} />,
};

Meaning that your stories would default to being rendered this way.

However since this is what you are going to do most of the time (spreading args into your component properties), CSF 3.0 introduces automatic default render functions, which means you do not even need to specify this default render function yourself: the runtime has defaults for the detected framework.

Note that the framework will be detected considering either of those two:

  • import of @storybook/XXX where XXX is the framework you're targeting (like @storybook/react)
  • the presence of the field framework on the default export, possible values being the same as the @storybook/XXX libraries. For example:
export default {
  framework: 'react',
};

Play functions

Play functions allow you to add automated interactions to your stories.

Defining those interactions is done in the play field of your story. Here is a simple example that will yield a story clicking twice on the "Hello" button:

import { screen } from '@testing-library/dom';
import userEvent from '@divriots/play-user-event';

export const clickPrimary = {
  args: { variant: 'primary' },
  play: () => {
    setTimeout(async () => {
      await userEvent.click(screen.getByText('Hello'));
      await userEvent.click(screen.getByText('Hello'));
    }, 1000);
  },
};

We recommend the use of @divriots/play-user-event as it will allow you to have visual feedback on those interactions.

References

Quick example

index.stories.js

import './index.ts';
import { html } from 'lit-html';

export default {
  parameters: {
    backgrounds: {
      default: 'dark',
    },
    layout: 'centered',
  },
};

export const story1 = () => html` <name-tag>John Foo</name-tag> `;
export const story2 = () => html` <name-tag>John Bar</name-tag> `;
story2.parameters = { backgrounds: { default: 'light' } };