Now that we have Storybook installed in our project, let's understand what's going on with the examples that it came with.
If you don't have Storybook running yet, make sure to run it with:
yarn storybook
Component Story Format (CSF)#
Storybook supports Component Story Format (CSF), an open standard based on ES6 modules which was introduced in Storybook 6.0 as the main way to write stories.
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.
Default export#
The default export is what we call Meta, the metadata that describes and configures a component. This is the place where you can set the title, which will be shown in the list of stories, or set extra configuration, which can be used by addons.
Let's start by adding the following:
// Button.stories.tsx
import { ComponentMeta } from '@storybook/react'
import { Button } from './Button'
export default {
title: 'Example/Button',
component: Button,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof Button>; // ComponentMeta automatically infers the props from Button
This is telling Storybook to display Example/Button
in the sidebar. Storybook uses the property component
to read metadata about the component, which is important internally and also for a few addons. The ComponentMeta
type automatically infers the props from the Button
component and provides autocompletion for args and argTypes. This is going to be useful soon, we'll circle back to that.
Named exports#
The second piece that a story file needs is a named export. Storybook expects the functions that you export in a story file to represent a state of a component, and that's what we call a story. The story is function that renders the component and optionally has annotations, like args:
// Button.stories.tsx
import { ComponentMeta, ComponentStory } from '@storybook/react'
import { Button } from './Button'
export default {
title: 'Example/Button',
component: Button,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof Button>;
// Common template defined which receives args and passes down to component
const Template: ComponentStory<typeof Button> = (args) => <Button {args} />;
// Primary variant, which clones the Template and sets specific args
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};
// Secondary variant, which clones the Template and sets specific args
export const Secondary = Template.bind({});
Secondary.args = {
label: 'Button',
};
// ... more stories
As a result, we can see that in Storybook, we have Example/Button on the sidebar, and it presents four stories, one for each named function in the file. This is a great way for developers and designers to understand the possible states of our component!

Let's add a new story to this file. Let's say we want a story to represent the Button with a red background. We create a new named export called Red
, which clones the Template
defined previously, and adds args for the props we want:
// Button.stories.tsx
export const Red = Template.bind({});
Red.args = {
label: 'Button',
backgroundColor: 'red',
};
We should now have a new story for that visual state in Storybook. Nice!

What about args?#
Args is a concept in Storybook that represents data which can be passed around. This helps us create a master template that can be reused in every story, where each story clones that template and just sets different args. Let's look at the Template
function again:
// Common template defined which receives args and passes down to component
const Template: ComponentStory<typeof Button> = (args) => <Button {args} />;
Storybook will make sure that the args we set for the Red
story will be passed down to that render function. You could think of them as props. Storybook comes with an addon called Controls, which detects the args for a given story and provides a panel where you can tweak with them.

And what about argTypes?#
Each of these args have types (e.g. string, boolean, etc.), and the Controls addon uses these types to provide you the correct element in its panel. These types are called argTypes, and they are automatically processed by Storybook using the Component
declared in the Meta (which is why it's important to declare Component
in default export!).
However, there are cases in which you want something more complex in the Controls panel, like backgroundColor
being a color picker rather than a string field, like label
. In scenarios like these, you can define argTypes manually to override the default behavior of controls. This was actually already done in this file, and you can see it in the Meta:
// Button.stories.tsx
export default {
title: 'Example/Button',
component: Button,
argTypes: {
// set the type of backgroundColor to be a color picker
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof Button>;
When setting argTypes in the Meta level, it will be applied to every story. You can also set argTypes to a story level. Let's make the argType of the Red
story contain a radio group with a few color as options:
export const Red = Template.bind({});
Red.args = {
backgroundColor: 'red',
label: 'Button',
};
Red.argTypes = {
backgroundColor: {
control: 'inline-radio',
options: ['red', 'green', 'blue']
}
};
We notice that the panel now updates with the set of colors we defined:

Args have a cascading behavior#
In Storybook, most of the properties can be set in a story level but also in a component level, like you just saw with argTypes. In every Button story, we see label: 'Button'
being repeated. If you're sure that property should always be applied to every story, you can set that arg at the component level instead.
In order to set properties at the component level, you add them to the Meta (the default export). These properties will then be applied to every single story in your stories file. To reuse common args, all you have to do is add the args
property to the Meta.
Applying args in a component level#
Given that information, we can move the label
arg that is defined in every story to be set in the Meta instead, and remove it from every story as it won't be necessary anymore:
// Button.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Button } from './Button';
export default {
title: 'Example/Button',
component: Button,
argTypes: {
backgroundColor: { control: 'color' },
},
// Set the label arg in the component level
args: {
label: 'Button',
},
} as ComponentMeta<typeof Button>;
const Template: ComponentStory<typeof Button> = (args) => <Button {args} />;
export const Primary = Template.bind({});
Primary.args = {
primary: true,
// label: 'Button', // remove this!
};
export const Secondary = Template.bind({});
// Secondary.args = { // remove this!
// label: 'Button',
// };
export const Large = Template.bind({});
Large.args = {
size: 'large',
// label: 'Button', // remove this!
};
export const Small = Template.bind({});
Small.args = {
size: 'small',
// label: 'Button', // remove this!
};
export const Red = Template.bind({});
Red.args = {
backgroundColor: 'red',
// label: 'Button', // remove this!
};
Red.argTypes = {
backgroundColor: {
control: 'inline-radio',
options: ['red', 'green', 'blue']
}
}
The result will be the same, but you won't ever need to pass down the properties manually like we did before. Just keep in mind that you won't always want properties defined in a component level, so there are times that it's best to define per story basis.
What happens to complex args?#
Args are cascading, so they will be sent down to every story and merged with the args
set in those stories. This means that you can override a single key of a complex object, and it will retain the rest of the properties defined at the component level.
Let's look at an example to visualize this behavior better:
export default {
args: { // Define args to be applied in every story of this component
user: {
name: 'John Doe',
age: 30,
}
}
}
// will use args from Meta. args become { user: { name: 'John Doe', age: 30 } }
export const Default = Template.bind({})
// merge args with overriden properties. args become { user: { name: 'John Doe', age: 25 } }
export const WithDifferentData = Template.bind({})
WithDifferentData.args = {
user: {
age: 25
}
}
Typescript types#
As you noticed, we used two types from @storybook/react
: ComponentMeta and ComponentStory.
These types not only give you autocompletion for Meta
and Story
attributes, but they also automatically infer the props of components passed into the generics and reflect those types in the args
property. This is great because this way you don't need to export the type of props of your component just to satisfy Storybook!
We see that the ComponentStory
type is only defined in the Template
function, but it's inferred correctly in any function that clones the Template
. This is only possible in projects that contain either strict
or strictBindApplyCall
modes set to true in their tsconfig.json
file. This example project already has this configuration, so you don't need to worry about it, but if you end up not having type inference in any other project, this might be the reason.
// tsconfig.json
{
"compilerOptions": {
// ...
"strict": true, // You need either this option
"strictBindCallApply": true // or this option
// ...
}
// ...
}
Introduction.stories.mdx#
The Introduction page is a special example that shows that Storybook can also handle MDX files for documentation purposes. This means you can write markdown and React components in the same file. It's a great way to write documentation, as well as onboarding pages with helpful resources, just like the Introduction page illustrates. It also has concepts of Meta
and Story
, but they are written differently. Most of the concepts we will see in this course (parameters, decorators, etc.) can be applied to MDX as well.
What's next?#
An important feature in Storybook is called decorator. In the next lesson, we will learn how to add extra markup/functionality to components by creating our own decorators.