Guide for Storybook
1. Getting started
1.1. Installation
bluh bluh bluh
1.2. Configure Storybook with Tailwind
To have Storybook and tailwind working together is rather simple.
All Storybook needs is a css, generated by tailwind as an output, as its input.
That same css file needs to be imported in the preview.js
configuration file of Storybook:
import '../styles/tailwind.css';
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
This will make Tailwind’s style classes available to all of your stories.
To generate that css file with tailwind, all you gotta do is wrote a script that does so:
{
"pre-storybook-prod": "npx tailwindcss -o ./styles/tailwind.css --minify"
}
This is for production.
For development, you would most-likely want a watch
version of that script, so that you could make changes to the source code, and see changes in real-time:
{
"pre-storybook-dev": "npx tailwindcss -i ./src/index.css -o ./styles/tailwind.css --watch"
}
1.3. Configure Storybook With Dark Mode
First of all, update your tailwind.config.js file to change themes based on a class or data-attribute. This example uses a data-attribute.
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
// Toggle dark-mode based on .dark class or data-mode="dark"
darkMode: ['class', '[data-theme="dark"]'],
theme: {
extend: {},
},
plugins: [],
};
Next, install the @storybook/addon-themes addon to provide the switcher tool.
npm i -D @storybook/addon-themes
Then, add following content to .storybook/main.js:
export default {
addons: ['@storybook/addon-themes'],
};
Toggle themes by class name
Add the withThemeByClassName decorator to your Storybook from @storybook/addon-themes:
import { withThemeByClassName } from '@storybook/addon-themes';
/* snipped for brevity */
export const decorators = [
withThemeByClassName({
themes: { light: 'light', dark: 'dark' },
defaultTheme: 'light',
}),
];
Toggle themes by data-attribute
Add the withThemeByDataAttribute decorator to your Storybook from @storybook/addon-themes:
import { withThemeByDataAttribute } from '@storybook/addon-themes';
/* snipped for brevity */
export const decorators = [
withThemeByDataAttribute({
themes: {
light: 'light',
dark: 'dark',
},
defaultTheme: 'light',
attributeName: 'data-mode',
}),
];
2. The *.stories.jsx Files
Storybook scans your project and looks for files which end with: .stories.js
, .stories.jsx
, .stories.ts
, .stories.tsx
.
Notice how it has stories
in it's path, in plural, to note that each file representing a component can export multiple stories. A *.stories.js file defines all the stories for a component. Each story has a corresponding sidebar item in the Storybook app. When you click on a story, it renders in the Canvas an isolated preview iframe.
USE ONLY .stories.jsx
with jsx
extension!!
If you use .js
extension, than jsx you write would result in Storybook crashing! With a useless explanation as to why!
3. A Component's Meta
Each .stories.jsx
file must include a Component's meta
, and export default it.
The meta
is simply a javascript object with properties.
At the very least, the meta
object must contain the component
key, which points to the actual component.
The default export metadata controls how Storybook lists your stories and provides information used by addons. For example, here’s the default export for a story file Button.stories.js|ts:
import Button from './Button';
export default {
component: Button,
};
Starting with Storybook version 7.0, story titles are analyzed statically as part of the build process. The default export must contain a title property that can be read statically or a component property from which an automatic title can be computed. Using the id property to customize your story URL must also be statically readable.
4. Layout Centered
Another nice-to-have key inside meta is the parameters.layout
, which tells Storybook where to render the component on the screen. By default, it renders it on the top-left, but it would be nice to have it centered, right?
To do so, simply add:
import Button from './Button';
export default {
component: Button,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
layout: 'centered',
},
};
5 Writing a Story
5.1. Introduction
A story
is merely a javascript object which hold an args
key. combination of values for the component's props, which describes how to render the component.
A story
needs to be named-exported from the *stories.js
file.
A story
with an empty as an object, will simply mean that all the component's props are undefined.
The variable name holding the story
will be the name presented in the Storybook app, describing that story
, so it's a good idea to have it uppercased.
If a stories.js file does not export a single story, no visuals of that component would appear in the Storybook app. It would be like the component doesn't even exist.
import { YourComponent } from './YourComponent';
// 👇 This default export determines where your story goes in the story list
export default {
component: YourComponent,
};
export const FirstStory = {
args: {
// 👇 The args you need here will depend on your component
},
};
5.2. Defining stories
Use the named exports of a file to define your component’s stories. We recommend you use UpperCamelCase for your story exports. Here’s how to render Button
in the "primary" state and export a story called Primary
.
import { Button } from './Button';
export default {
component: Button,
};
/*
*👇 Render functions are a framework specific feature to allow you control on how the component renders.
* See https://storybook.js.org/docs/api/csf
* to learn how to use render functions.
*/
export const Primary = {
render: () => <Button primary label='Button' />,
};
5.3. Rename stories
You can rename a story to give it a more accurate display name using the name
property on your story
object.
Here's an example:
import { Button } from './Button';
export const Primary = {
name: 'I am the primary',
render: () => <Button primary label='Button' />,
};
export default { component: Button };
5.4. Story level args
Obviously we've seen those already. These are the args
defined on each story:
const Default = {
name: 'Default Case',
args: {
isPrimary: true,
color: 'blue',
disabled: false,
},
};
These are the strongest args, and will take precedence over Component level args & global level args.
5.5 Story Custom Render
Here's how you can have a custom-made renderer for your story:
export const Primary = {
args: { isPrimary: true, label: 'Button' },
render: (args) => (
const { backgroundColor, isPrimary, label, size } = args;
// 👇 Assigns the function result to a variable
const someFunctionResult = someFunction(propertyA, propertyB);
<Button {...args} someComplexProp={someFunctionResult}/>
),
};
This could be useful in multiple cases.
For example, in a case where you need the parent to have dir="rtl"
.
5.6. Hide an arg's controller
If you wish to hide a certain arg, or I should say a controller for an arg, there's a very easy way to do so. Let's say you have a Component with a prop named testId
, and you decided you don't need a controller for it.
You have two options as to how you can hide a prop's controller.
-
- The direct way: using
meta.argTypes.propName1.table.disable
- The direct way: using
The direct way is using the meta.argTypes.propName1.table.disable
key and provide a boolean false to hide it from view.
// <typeof Button>
/** @type {import('@storybook/react').Meta} */
export default {
title: 'Example/Button',
component: Button,
argTypes: { testId: { table: { disable: true } } },
};
-
- The indirect way: using
meta.parameters.controls.exclude
- The indirect way: using
The indirect way is using the meta.parameters.controls.exclude
key and provide a regex that catches the arg by its name, to hide it from view.
/** @type {import('@storybook/react').Meta} */
export default {
title: 'Example/Button',
component: Button,
parameters: {
controls: { exclude: /testId/g },
},
};
5.7 Storybook Controls
- Introduction
In this section you'll learn how to write Docs for your components.
Under Meta
, add an argsType
key, which should be an object.
Each key under argsType
is actually prop name
of your component.
/** @type {import('@storybook/react').Meta<typeof Button>} */
export default {
title: 'Example/Button',
component: Button,
parameters: { layout: 'centered' },
tags: ['autodocs'],
argTypes: {
propName1: { ... },
propName2: { ... },
},
};
- Adding docs
Every prop name
cab have basic metadata such as name, description, and defaultValue.
/** @type {import('@storybook/react').Meta<typeof Button>} */
export default {
title: 'Example/Button',
component: Button,
parameters: { layout: 'centered' },
tags: ['autodocs'],
argTypes: {
propName1: {
name: 'This will replace `propName1`',
description: 'This is the description for `propName1`',
defaultValue: 111,
control: ...,
},
},
};
- Choosing the control type
- Control Type 1:
boolean
Provides a toggle for switching between possible states.
export default {
component: Button,
argTypes: {
propName1: {
control: 'boolean',
},
},
};
- Control Type 2:
text
Provides a freeform text input.
export default {
component: Button,
argTypes: {
propName1: 'text',
},
};