← Back to list

storybook
by mgd34msu
Plug in, Receive good vibes.
⭐ 2🍴 0📅 Jan 25, 2026
SKILL.md
name: storybook description: Develops and documents UI components in isolation with Storybook's interactive workshop environment. Use when building component libraries, documenting design systems, or when user mentions Storybook, component documentation, or UI development.
Storybook
Industry-standard workshop for building, documenting, and testing UI components in isolation.
Quick Start
# Install in existing project
npx storybook@latest init
# Start Storybook
npm run storybook
Project Structure
.storybook/
main.ts # Configuration
preview.ts # Global decorators and parameters
preview-head.html # Custom head content
src/
components/
Button/
Button.tsx
Button.stories.tsx
Button.module.css
Configuration
main.ts
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-onboarding',
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
staticDirs: ['../public'],
typescript: {
reactDocgen: 'react-docgen-typescript',
},
};
export default config;
preview.ts
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
import '../src/styles/globals.css';
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a1a' },
{ name: 'gray', value: '#f5f5f5' },
],
},
layout: 'centered',
},
decorators: [
(Story) => (
<div style={{ margin: '1rem' }}>
<Story />
</div>
),
],
globalTypes: {
theme: {
name: 'Theme',
description: 'Global theme',
defaultValue: 'light',
toolbar: {
icon: 'circlehollow',
items: ['light', 'dark'],
showName: true,
},
},
},
};
export default preview;
Writing Stories
Basic Story
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
},
size: {
control: 'radio',
options: ['sm', 'md', 'lg'],
},
onClick: { action: 'clicked' },
},
};
export default meta;
type Story = StoryObj<typeof meta>;
// Stories
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Button',
},
};
export const Secondary: Story = {
args: {
variant: 'secondary',
children: 'Button',
},
};
export const Large: Story = {
args: {
size: 'lg',
children: 'Large Button',
},
};
export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled',
},
};
With Decorators
export const InDarkMode: Story = {
decorators: [
(Story) => (
<div className="dark bg-gray-900 p-4">
<Story />
</div>
),
],
args: {
children: 'Dark Mode Button',
},
};
// Decorator with args access
export const WithContext: Story = {
decorators: [
(Story, context) => (
<ThemeProvider theme={context.globals.theme}>
<Story />
</ThemeProvider>
),
],
};
With Play Functions (Interaction Testing)
import { within, userEvent, expect } from '@storybook/test';
export const Filled: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Type in input
const input = canvas.getByPlaceholderText('Enter email');
await userEvent.type(input, 'test@example.com', { delay: 100 });
// Click button
const button = canvas.getByRole('button', { name: /submit/i });
await userEvent.click(button);
// Assert
await expect(canvas.getByText('Success!')).toBeInTheDocument();
},
};
export const WithValidation: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Submit empty form
await userEvent.click(canvas.getByRole('button'));
// Check for error
await expect(canvas.getByText('Email is required')).toBeVisible();
},
};
Async Stories
export const WithData: Story = {
loaders: [
async () => ({
users: await fetch('/api/users').then((r) => r.json()),
}),
],
render: (args, { loaded: { users } }) => (
<UserList users={users} {...args} />
),
};
// Or with render function
export const Loading: Story = {
render: () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
if (!data) return <Skeleton />;
return <Component data={data} />;
},
};
Controls
ArgTypes
const meta: Meta<typeof Button> = {
argTypes: {
// Control types
variant: {
control: 'select',
options: ['primary', 'secondary'],
description: 'Visual style variant',
table: {
type: { summary: 'string' },
defaultValue: { summary: 'primary' },
},
},
size: {
control: 'inline-radio',
options: ['sm', 'md', 'lg'],
},
disabled: {
control: 'boolean',
},
count: {
control: { type: 'number', min: 0, max: 100, step: 1 },
},
color: {
control: 'color',
},
date: {
control: 'date',
},
items: {
control: 'object',
},
label: {
control: 'text',
},
// Disable control
onClick: {
control: false,
},
// Categorize in table
theme: {
table: {
category: 'Styling',
},
},
},
};
Documentation
MDX Documentation
{/* Button.mdx */}
import { Canvas, Meta, Story, Controls, ArgTypes } from '@storybook/blocks';
import * as ButtonStories from './Button.stories';
<Meta of={ButtonStories} />
# Button
Buttons trigger actions when clicked.
## Usage
```tsx
import { Button } from '@/components/Button';
<Button variant="primary" onClick={handleClick}>
Click me
</Button>
Examples
Primary Button
All Variants
Props
Component Documentation
// Button.tsx - JSDoc for autodocs
interface ButtonProps {
/** The visual style variant */
variant?: 'primary' | 'secondary' | 'danger';
/** Button size */
size?: 'sm' | 'md' | 'lg';
/** Whether the button is disabled */
disabled?: boolean;
/** Click handler */
onClick?: () => void;
/** Button content */
children: React.ReactNode;
}
/**
* Primary UI component for user interaction.
*
* @example
* ```tsx
* <Button variant="primary" onClick={handleClick}>
* Click me
* </Button>
* ```
*/
export function Button({ variant = 'primary', size = 'md', ...props }: ButtonProps) {
// ...
}
Addons
Essential Addons (Included)
// Already included with @storybook/addon-essentials:
// - Controls: Interactive props
// - Actions: Event logging
// - Docs: Auto-documentation
// - Viewport: Responsive testing
// - Backgrounds: Background colors
// - Toolbars: Global controls
// - Measure: Layout measurement
// - Outline: DOM outlines
Accessibility Testing
npm install -D @storybook/addon-a11y
// .storybook/main.ts
addons: ['@storybook/addon-a11y'],
// In story
export const Accessible: Story = {
parameters: {
a11y: {
config: {
rules: [{ id: 'color-contrast', enabled: true }],
},
},
},
};
Interactions
npm install -D @storybook/addon-interactions @storybook/test
import { within, userEvent, expect, fn } from '@storybook/test';
const meta: Meta<typeof Form> = {
args: {
onSubmit: fn(),
},
};
export const Submitted: Story = {
play: async ({ args, canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByLabelText('Email'), 'test@test.com');
await userEvent.click(canvas.getByRole('button'));
await expect(args.onSubmit).toHaveBeenCalled();
},
};
Testing Stories
With Test Runner
npm install -D @storybook/test-runner
# Run tests
npx test-storybook
# Watch mode
npx test-storybook --watch
With Vitest
// Button.test.tsx
import { composeStories } from '@storybook/react';
import { render, screen } from '@testing-library/react';
import * as stories from './Button.stories';
const { Primary, Secondary } = composeStories(stories);
test('Primary button renders correctly', () => {
render(<Primary />);
expect(screen.getByRole('button')).toHaveClass('btn-primary');
});
test('runs play function', async () => {
const { container } = render(<Secondary />);
await Secondary.play?.({ canvasElement: container });
});
Visual Testing with Chromatic
# Install
npm install -D chromatic
# Run
npx chromatic --project-token=<token>
# .github/workflows/chromatic.yml
name: Chromatic
on: push
jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
- run: npm ci
- uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
Build and Deploy
# Build static Storybook
npm run build-storybook
# Output in storybook-static/
# Deploy to GitHub Pages
# .github/workflows/deploy.yml
name: Deploy Storybook
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run build-storybook
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./storybook-static
Reference Files
- patterns.md - Common Storybook patterns
- testing.md - Testing strategies with Storybook
Score
Total Score
60/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon