Storybook CSF3 is here

Next gen story format to make you more productive

Michael Shilman
Storybook
Published in
6 min readJan 26, 2023

--

Stories help you understand how a component behaves in different scenarios. Companies such as GitLab, Codecademy, Audi, and Twilio use stories to document and test their UI components.

Component Story Format (CSF) is the universal file format for stories. The latest version, CSF3, significantly reduces the amount of boilerplate code which makes stories more concise and faster to write than earlier versions.

CSF3 was first announced over 18 months ago in beta. Since then, community feedback helped us address issues and refine the format. I'm excited to announce the full release of CSF3. Starting with Storybook 7, CSF3 will be the default.

Improvements include:

  • ♻️ Spreadable story objects to easily extend stories
  • 🌈 Default render functions for brevity
  • 📓 Automatic titles for convenience
  • ▶️ Play functions for scripted interactions and tests
  • ✅ 100% backwards compatible with CSF 2

Read on to learn more about the format, what’s changed since the original announcement, and how to make the most of it in Storybook 7.

Wait, but why?

Developing UI components outside of your application is the best way to create high-quality components. Storybook pioneered this style of Component-driven Development (CDD).

Stories are now used for visual review by designers and product managers, as well as for design system documentation, automated testing, and even generating design assets from production components.

It's no surprise that Storybook is used to build many of the world's most popular UIs at Shopify, IBM, and Salesforce.

CSF3 is the next evolution of stories—easier to write and maintain

Much like its predecessor, CSF3 is based on ESM. The default export contains information about the component, and one or more named exports—stories—capture different component states. The main difference is that stories are now objects, and you can attach a play function to each story to simulate user interactions.

CSF3 is fully backwards compatible with CSF2, which is still supported in Storybook 7. We’ve even back ported play functions to CSF2.

We recommend migrating because CSF3 is more expressive and maintainable with less boilerplate code required from you. In most cases, you can automatically migrate from CSF 2 to 3 using a codemod.

CSF3 feature recap

Large projects can consist of hundreds of components and thousands of stories. When you write this many stories, ergonomic improvements result in noticeable quality of life improvements. Our goal was to streamline the story format to make writing, reading, and maintaining stories easier.

Let's see what CSF3 looks like in a hypothetical RegistrationForm component.

The default export declares the component that you’re writing stories for:

// RegistrationForm.stories.js

import { RegistrationForm } from './forms/RegistrationForm';

export default {
title: 'forms/RegistrationForm',
component: RegistrationForm,
};

And stories are now objects:

export const EmptyForm = {
render: (args) => <RegistrationForm {...args} />,
args: { /* ... */ },
parameters: { /* ... */ },
};

Default render functions for brevity. 90% of the time, writing a story is just passing some inputs to your component in a standard way.

In our RegistrationForm example, the render function is boilerplate. In CSF3, if you don’t specify the render function, simplifying your code.

By default, each story renders the component and passes in all arguments.

export const EmptyForm = {
// render: (args) => <RegistrationForm {...args} />, -- now optional!
args: { /* ... */ },
parameters: { /* ... */ },
};

Spreadable story objects for reuse. When you’re trying to model complex states, it’s useful to be able to extend existing stories instead of writing them anew. Suppose you want to show the filled form, but in a different viewport:

export const FilledForm = {
args: {
email: 'marcus@acme.com',
password: 'j1287asbj2yi394jd',
}
};

export const FilledFormMobile = {
...FilledForm,
parameters: {
viewports: { default: 'mobile' }
},
};

Play functions for scripted interactions. Some UI states are impossible to capture without user interaction. The play function runs after the story has been rendered, and uses testing-library to simulate user interactions. For example:

// RegistrationForm.stories.ts|tsx
import { userEvent, within } from '@storybook/testing-library';

// ...

export const FilledForm = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

const emailInput = canvas.getByLabelText('email', {
selector: 'input'
});
await userEvent.type(emailInput, 'example-email@email.com', {
delay: 100
});

const passwordInput = canvas.getByLabelText('password', {
selector: 'input'
});
await userEvent.type(passwordInput, 'ExamplePassword', {
delay: 100
});

const submitButton = canvas.getByRole('button');
await userEvent.click(submitButton);
},
};

Automatic titles for convenience. In CSF, a story’s title determines where it shows up in the navigation hierarchy in the UI. In CSF3, the title can be automatically generated based on the file’s location relative to the root. Less to type, and nothing to update if you reorder your files.

export default {
// title: 'forms/RegistrationForm' -- optional
component: RegistrationForm,
};

For an in-depth description of CSF3’s features and rationale, and exactly how it differs from CSF2, please see the original CSF3 post.

Changes to the original

Over the past year and half, users have been testing CSF3 in their projects. Based on feedback we’ve made a few changes from the original.

Better TypeScript types. We’ve updated the Meta/Story types in 7.0 to support type safety and autocompletion for stories. Stay tuned for a dedicated post about this in the coming weeks.

Updated autotitle heuristics. Autotitle generates the “title” (path in the Storybook sidebar) based on a CSF file’s disk location. For example, if /project/path/src is the story root, /project/path/src/atoms/Button.stories.js would get the title atoms/Button. However, the naive heuristic doesn’t handle common patterns like atoms/Button/Button.stories.js or atoms/Button/index.stories.js. We updated the heuristics to account for this. For full migration instructions and several other autotitle improvements including better prefix handling and capitalization, see MIGRATION.md.

Upgraded documentation and CLI templates. Last but not least, we’ve upgraded the official documentation for 7.0 with CSF3 source snippets. We’ve also updated the CLI to generate CSF3 for new projects.

Upgrade to CSF3 today

CSF3 is fully backwards compatible, so your existing CSF stories still work fine without modification. We won’t deprecate the old format any time soon. However, CSF3 is a big step forward, and we recommend upgrading your stories as part of upgrading to Storybook 7.0 (SB7).

To upgrade to SB7, see our SB7 migration guide. After your project is upgraded, you can optionally migrate your CSF stories to CSF3 using the following codemod. Be sure to update the glob to include the files you want to update.

npx storybook@next migrate csf-2-to-3 --glob="**/*.stories.js"

Get involved

Component Story Format (CSF) helps you develop, test, and document your components in isolation. With CSF3 comes improved ergonomics to help you write more stories with less effort.

CSF3 was developed by Michael Shilman (me!), Kasper Peulen, Tom Coleman, and Pavan Sunkara with testing and feedback from the entire Storybook community.

Storybook is the product of over 1600 community committers. Join us on GitHub or chat with us on Discord. Finally, follow @storybookjs on Twitter for the latest news.

--

--

Michael Shilman
Storybook

energetic engineer 👷 frequent flyer 🚀 eternal optimist ❤️