TABLE OF CONTENTS

Storybook Configuration With CRA and TypeScript

storybook configuration

Intro

If you want to start a Storybook dvelopment, you should do this with configuration. Let me show you step-by-step how to do it based on create-react-app and TypeScript example.

CRA:

Base configuration:

You can add a storybook to other frameworks like Gatsby.js, Next.js, etc. as well, but in this example, we will just use a simple CRA project with TypeScript. 

Here’s what you have to do:

yarn create react-app storybook-app --template typescript

Hint:  --template typescript adds typescript configuration to the project out of the box. You can configure tsconfig.json later with the properties that you need (if you know what you’re doing of course).

Go to your projects directory and type this in your terminal to run the app:

yarn run start

It should automatically open on port 3000.

Styles:

And pretty much that’s all when it comes to project base configuration. 

You may also want to add a few libraries that can help you with CSS, like Styled Components for example, so the next steps are: 

  • Add styled-components (or other library to prepare styles) to the project:
yarn add styled-components
yarn add -D @types/styled-components

Hint: dependencies are required to run, devDependencies only to develop, e.g: unit tests, CoffeeScript to JavaScript transpilation, minification, …

  • You can also add CSS normalizer if you want:
yarn add styled-normalize

Project structure:

We’re going to use Atomic Design approach in this example, so we will need to create 3 directories inside src/components:

  • atoms
  • molecules
  • organisms

We can also add a directory called styles to store components used for typography, layout, etc.

Storybook

When the project base is set, we can finally add Storybook – it’s required to have a project base first, you cannot just run storybook without it:

Storybook needs to be installed into a project that is already setup with a framework. It will not work on an empty project (Storybook info)

Installation:

Type the command below in your terminal (inside a directory with your project base created earlier with CRA – it should be in the root of this directory):

npx sb init

This will install all the dependencies needed.

You don’t even have to install any dependencies to use TypeScript with Storybook – everything is already there.

Run:

yarn run storybook

To see Storybook default view with some demo components added.

Storybook directory structure:

When you will open .storybook directory, you will see 2 files added:

  • main.js
  • preview.js

You can also add a file called:

  • manager.js (optional)

main.js

This file controls the behavior of the Storybook server –  you must restart Storybook’s process when you change it. It is composed of four key fields:

  • stories – define a path to the stories that you want to add to the Storybook
"stories": [
   "../src/**/*.stories.mdx",
   "../src/**/*.stories.@(js|jsx|ts|tsx)"
 ]
"addons": [
   "@storybook/addon-links",
   "@storybook/addon-essentials",
   "@storybook/preset-create-react-app"
]
  • webpackFinal – custom webpack configuration.
  • babel – custom babel configuration.

preview.js

To control the way stories are rendered and add global decorators and parameters, create a .storybook/preview.js file. This is loaded in the Canvas tab, the “preview” iframe that renders your components in isolation. Use preview.js for global code (such as CSS imports or JavaScript mocks) that applies to all stories. (Storybook)

It exports the following keys:

export const decorators = [
  (Story) => (
    <>
      <Normalize />
      <Story />
    </>
  ),
];
export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
};

manager.js – optional

You can also add manager.js file to control the behaviour of Storybook’s UI (including Storybook theme)

import { addons } from '@storybook/addons';
import { themes } from '@storybook/theming';

addons.setConfig({
  theme: themes.dark,
});

Want to use Storybook in your project?

Stories:

File name

Inside a directory with the component that you created, and you want to display on Storybook you have to add a file which name starts with components’ name and ends with .stories.tsx

Button.stories.tsx

File content

Base

Base content looks like this:

import React from "react";
import { Meta, Story } from "@storybook/react/types-6-0";
import Button from ".";
export default {
  title: "Atoms/Button",
  component: Button,
} as Meta;
export const Base: Story = () => <Button>Button content</Button>;
  • title – the name of the section where our component will be visible
    • when adding a name with “/” it creates a subsection. In this example Atoms will be the main section and Button will be added under it as its subsection
  • component – a component that we want to display
export const Base: Story = () => <Button>Button content</Button>;
  • Base is the name of our story
    • if we have more examples to show, we can add multiple story components with different names – like Primary, Secondary, etc.:
export const Primary = () => <Button background="#ff0" label="Button" />;
export const Secondary = () => <Button background="#ff0" label="😄👍😍💯" />;
export const Tertiary = () => <Button background="#ff0" label="📚📕📈🤓" />;
Template

We can also create a template for our components stories if there are some common props that we want to show. We can then add a story for each variant and apply only the needed props without repeating any other props.

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

After preparing a Template we can use it like this:

export const Primary = Template.bind({});
Primary.args = {
  mode: ButtonMode.primary,
  children: "Button content",
};

export const Secondary = Template.bind({});
Secondary.args = {
  mode: ButtonMode.secondary,
  children: "Button content",
};

Controls

Previously there was an addon called knobs – it allowed to edit some content of the component (its props, etc.) in the Storybook app, so you could change the text inside a button to check how it behaves with less or more text inside, make it disabled, etc. 

The successor of knobs is an addon called controls. It has similar functionalities, but we have to define editable props first with args or argTypes. We can use such types:

Data TypeControl TypeDescriptionOptions
arrayarrayserialize array into a comma-separated string inside a textboxseparator
booleanbooleancheckbox input
numbernumbera numeric text box inputmin, max, step
rangea range slider inputmin, max, step
objectobjectjson editor text input
enumradioradio buttons inputoptions
inline-radioinline radio buttons inputoptions
checkmulti-select checkbox inputoptions
inline-checkmulti-select inline checkbox inputoptions
selectselect dropdown inputoptions
multi-selectmulti-select dropdown inputoptions
stringtextsimple text input
colorcolor picker input that assumes strings are color values
datedate picker input

We can use it like this:

export default {
  title: "Atoms/Button",
  component: Button,
  argTypes: {
    disabled: {
      name: "Is disabled",
      control: {
        type: "boolean",
      },
      defaultValue: false,
    },
    mode: {
      table: { disable: true },
    },
    children: {
      name: "Content",
      control: {
        required: true,
      },
    },
    size: {
      name: "Size",
      control: {
        type: "select",
        options: [ButtonSize.big, ButtonSize.medium, ButtonSize.small],
      },
      defaultValue: ButtonSize.big,
    },
  },
} as Meta;

We used boolean and select in this example. children prop don’t have a control type defined and it renders text field. If there is a props that we don’t want to display we can set it just like mode:

mode: {
  table: { disable: true },
},

When adding other variants we might need to change just one prop, to do so we can just modify it with args.

export const Primary = Template.bind({});
Primary.args = {
  mode: ButtonMode.primary,
  children: "Button content",
};

Actions

Components like buttons always have some onClick actions passed to them. 

We can also simulate those actions in Storybook using Actions addon:

export default {
  title: "Atoms/Button",
  component: Button,
  argTypes: {
    onClick: { action: 'clicked' }
  },
} as Meta;
  • action – what should be displayed after onClick action

It is also possible to detect if your component is emitting the correct HTML events using the parameters.actions.handles parameter.

export default {
  title: 'Button',
  parameters: {
    actions: {
      handles: ['mouseover', 'click .btn'],
    },
  },
};

Backgrounds

Some components are light and need a dark background, some are dark and need a light background, and it’s also possible to set a needed background for each component. We can set it to every variant of the component in the story or a different one for each of them. Unfortunately there is a small bug right now that makes it impossible to use this addon without a small fix. To make it work we have to add:

.sb-show-main {
  background-color: transparent;
}

to Storybook styles. 

After that we will be able to use it like this:

export default {
  title: "Atoms/Button",
  component: Button,
  parameters: {
    backgrounds: {
      default: "light",
      values: [
        { name: "light", value: "#fff" },
        { name: "dark", value: "#000" },
      ],
    },
  },
} as Meta;

or like this (for single variant):

Secondary.parameters = {
  backgrounds: { default: 'light' }
};

Decorators

There is also an option to add some custom wrapper to each story. To do so we have to define a decorator:

export default {
  component: Button,
  decorators: [(Story) => <div style={{ margin: '3em' }}><Story/></div>]
}

To change the component position we can use a parameter called layout

It has the options to chose:

  • centered: center the component horizontally and vertically in the Canvas
  • fullscreen: allow the component to expand to the full width and height of the Canvas
  • padded: Add extra padding around the component
export default {
  title: "Atoms/Button",
  component: Button,
  parameters: {
    layout: "centered",
  }
 } as Meta;

Hierarchy:

Grouping

We can group some components using / just like in the example with the Button where we grouped it with Atoms

export default {
  title: "Atoms/Button",
  component: Button,
 } as Meta;

There is also a possibility to add more layers inside it, so there can be for example:

title: "Atoms/Button/Square",

etc. 

By default, the top-level grouping will be displayed as a “root” in the UI (the all-caps, non-expandable grouping in the screenshot above). If you prefer, you can configure Storybook to not show roots.

Sorting stories

By default, stories are sorted in the order in which they were imported. This can be overridden by adding storySort to the options parameters in your preview.js file.

export const parameters = {
  options: {
    storySort: (a, b) =>
      a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }),
  },
};

or

export const parameters = {
  options: {
    storySort: {
      method: '',
      order: [], 
      locales: '', 
    },
  },
};

FieldTypeDescriptionRequiredDefault ValueExample
methodStringTells Storybook in which order the stories are displayedNoStorybook configuration‘alphabetical’
orderArrayThe stories to be shown, ordered by supplied nameNoEmpty Array
[ ]
[‘Intro’, ‘Components’]
localesStringThe locale required to be displayedNoSystem localeen-US


We’re using Atomic Design approach, so let’s use it also when defying stories sort order:

export const parameters = {
  options: {
    storySort: {
      order: ['Styles', 'Atoms', 'Molecules', 'Organisms'],
    },
  }
};

Our sections will always be rendered in a defined order. If a new section that isn’t defined here will be added, it will be displayed after all the defined sections.

Article link copied

Close button