Now that component-based development is mainstream, efficiently developing, testing, and documenting standalone components has become an important challenge. Storybook is an open-source UI component development environment that lets you build and showcase components in isolation, free from business code. This article walks through setting up a complete Storybook development environment from scratch.
Project Initialization
Taking a React project as an example, initialize Storybook with npx:
npx -p @storybook/cli sb init
Once initialization finishes, the dependencies are installed automatically and a .storybook config directory plus a stories example directory are created. The project structure looks like this:
├── .storybook
│ ├── addons.js
│ ├── config.js
│ └── webpack.config.js
├── stories
│ └── index.stories.js
└── package.json
Writing Your First Story
A Story is a rendering of a component in a particular state. The recommended format is Component Story Format (CSF), a new format introduced in Storybook 5.2:
import React from 'react';
import Button from '../components/Button';
export default {
title: 'Components|Button',
component: Button,
};
export const Default = () => <Button>点击我</Button>;
Default.story = {
name: '默认状态',
};
export const Disabled = () => <Button disabled>不可点击</Button>;
export const Loading = () => <Button loading>加载中...</Button>;
Each exported function is an independent Story that you can view and interact with individually in the Storybook sidebar.
Using the Knobs Plugin for Dynamic Props
@storybook/addon-knobs is one of the most commonly used addons—it lets you adjust a component's props dynamically from the Storybook panel:
import React from 'react';
import { text, boolean, select, number } from '@storybook/addon-knobs';
import Button from '../components/Button';
export default {
title: 'Components|Button',
decorators: [withKnobs],
};
export const Playground = () => {
const label = text('Label', '按钮文字');
const disabled = boolean('Disabled', false);
const loading = boolean('Loading', false);
const size = select('Size', ['small', 'medium', 'large'], 'medium');
const type = select('Type', ['primary', 'default', 'danger'], 'default');
return (
<Button
disabled={disabled}
loading={loading}
size={size}
type={type}
>
{label}
</Button>
);
};
Register the addons in .storybook/addons.js:
import '@storybook/addon-knobs/register';
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
Using the Actions Plugin to Capture Events
The Actions addon lets you see the event callbacks a component fires right in the Storybook panel, which makes debugging interactions easier:
import React from 'react';
import { action } from '@storybook/addon-actions';
import Button from '../components/Button';
export const WithOnClick = () => (
<Button onClick={action('button-click')}>
点击查看事件
</Button>
);
export const WithFormSubmit = () => (
<form onSubmit={action('form-submit')}>
<input onChange={action('input-change')} />
<Button type="submit">提交</Button>
</form>
);
Configuring Custom Webpack
If your project uses CSS Modules, TypeScript, and the like, you'll need to extend Storybook's Webpack configuration:
// .storybook/webpack.config.js
const path = require('path');
module.exports = ({ config }) => {
// TypeScript 支持
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
{
loader: require.resolve('awesome-typescript-loader'),
},
],
});
// CSS Modules 支持
config.module.rules.push({
test: /\.module\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
},
},
},
],
include: path.resolve(__dirname, '../src'),
});
// 普通 CSS
config.module.rules.push({
test: /\.css$/,
use: ['style-loader', 'css-loader'],
exclude: /\.module\.css$/,
});
// 别名配置
config.resolve.alias = {
...config.resolve.alias,
'@': path.resolve(__dirname, '../src'),
};
config.resolve.extensions.push('.ts', '.tsx');
return config;
};
Adding the Docs Plugin
Storybook 5.2 introduced @storybook/addon-docs, which makes writing documentation more intuitive with the MDX format:
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import Button from '../components/Button';
<Meta title="Components|Button" component={Button} />
# Button 按钮
按钮用于触发一个操作,支持多种尺寸和状态。
## Basic Usage
<Preview>
<Story name="Default">
<Button>默认按钮</Button>
</Story>
<Story name="Primary">
<Button type="primary">主要按钮</Button>
</Story>
<Story name="Danger">
<Button type="danger">危险按钮</Button>
</Story>
</Preview>
## Props
<Props of={Button} />
Global Wrapping with Decorators
Decorators let you wrap every Story with shared context, such as a theme Provider, a Redux store, or a Router:
{% raw %}
// .storybook/config.js
import { addDecorator, configure } from '@storybook/react';
import { ThemeProvider } from '../src/theme';
import { MemoryRouter } from 'react-router-dom';
// 全局 Decorator
addDecorator(story => (
<ThemeProvider theme="light">
<MemoryRouter>
<div style={{ padding: '20px' }}>
{story()}
</div>
</MemoryRouter>
</ThemeProvider>
));
const req = require.context('../src', true, /\.stories\.(js|jsx|ts|tsx)$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
{% endraw %}
You can also add a Decorator at the individual Story level:
{% raw %}
export default {
title: 'Components|Modal',
decorators: [
Story => (
<div style={{ width: '600px', margin: '0 auto' }}>
<Story />
</div>
),
],
};
{% endraw %}
Organizing Story Directory Structure
It's recommended to organize Story files to mirror your actual component directory structure:
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.module.css
│ │ ├── Button.stories.tsx ← Story 文件放在组件旁边
│ │ └── index.ts
│ ├── Modal/
│ │ ├── Modal.tsx
│ │ ├── Modal.stories.tsx
│ │ └── index.ts
│ └── index.ts
Configure the file-matching rules in .storybook/config.js:
const req = require.context('../src', true, /\.stories\.(js|tsx)$/);
Integrating with Accessibility Checks
Storybook is a great fit as a documentation site for a component library. You can pair it with @storybook/addon-a11y to run accessibility checks:
// .storybook/config.js
import { withA11y } from '@storybook/addon-a11y';
addDecorator(withA11y);
This way every Story panel shows its accessibility check results, helping you keep components accessible.
Building Static Sites
Storybook can be built into a static HTML site, which makes it easy to deploy to any static hosting service:
npm run build-storybook
The build output lands in the storybook-static directory and can be deployed to GitHub Pages, Netlify, or an internal server.
{
"scripts": {
"storybook": "start-storybook -p 9009",
"build-storybook": "build-storybook -o docs",
"deploy-storybook": "storybook-to-ghpages"
}
}
Summary
- Storybook provides an isolated component development environment, independent of business context
- CSF (Component Story Format) is the recommended way to write stories
- The Knobs addon supports dynamically adjusting props, making interactive debugging easy
- The Actions addon can capture and display a component's event callbacks
- Custom Webpack config supports CSS Modules, TypeScript, and similar tooling
- Decorators can provide global or local context wrapping
- addon-docs supports writing component docs in MDX format
- It can be built into a static site for easy team sharing and deployment
