npx skills add https://github.com/dalestudy/skills --skill storybook使用最新的 Component Story Format 3.0。更简洁且类型安全。
// ❌ CSF 2.0 (旧版)
export default {
title: 'Components/Button',
component: Button,
};
export const Primary = () => <Button variant="primary">Click me</Button>;
// ✅ CSF 3.0 (推荐)
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta = {
component: Button,
tags: ['autodocs'], // 自动生成文档
args: {
variant: 'primary',
children: 'Click me',
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {};
export const Secondary: Story = {
args: {
variant: 'secondary',
},
};
将组件 Props 定义为 Args,以便在 Controls 面板中进行交互式操作。
默认值在args中声明 (❌ 禁止使用 argTypes.defaultValue)。在 Meta 的 args 中设置默认值,Controls 面板会自动选中该值
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
多个故事共用的 args 在 Meta(组件)级别声明,个别故事中只覆盖差异部分
// ❌ 硬编码的 Props export const Disabled: Story = { render: () => <Button disabled>Disabled</Button>, };
// ❌ 多个故事中重复相同的 args export const Primary: Story = { args: { children: 'Click me', variant: 'primary' }, }; export const Secondary: Story = { args: { children: 'Click me', variant: 'secondary' }, };
// ✅ Meta 中声明共用 args,故事中只覆盖差异点 const meta = { component: Button, args: { children: 'Click me', variant: 'primary', }, } satisfies Meta<typeof Button>;
export const Primary: Story = {};
export const Secondary: Story = { args: { variant: 'secondary' }, };
export const Disabled: Story = { args: { disabled: true }, };
直接以字符串形式指定 title 会导致类型不安全,并且在组件名称/路径更改时容易失去同步。Storybook 会根据文件路径自动推断侧边栏层级,因此省略 title。
// ❌ 直接指定 title — 类型不安全且存在同步中断风险
const meta = {
title: 'Components/Button',
component: Button,
} satisfies Meta<typeof Button>;
// ✅ 省略 title — 从文件路径自动推断
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
使用 satisfies 关键字同时实现类型检查和类型推断。
// ❌ 无法进行类型推断
const meta: Meta<typeof Button> = {
component: Button,
};
// ✅ 类型检查和推断均可实现
const meta = {
component: Button,
args: {
size: 'md',
variant: 'primary',
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
通过 Decorator 应用公共包装器或 Provider。
// 为个别故事应用 Decorator
export const WithTheme: Story = {
decorators: [
(Story) => (
<ThemeProvider theme="dark">
<Story />
</ThemeProvider>
),
],
};
// 为所有故事应用 Decorator
const meta = {
component: Button,
decorators: [
(Story) => (
<div style={{ padding: '3rem' }}>
<Story />
</div>
),
],
} satisfies Meta<typeof Button>;
const meta = {
component: Button,
parameters: {
layout: 'centered', // 将故事居中对齐
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#000000' },
],
},
},
} satisfies Meta<typeof Button>;
// 在个别故事中覆盖
export const OnDark: Story = {
parameters: {
backgrounds: { default: 'dark' },
},
};
Storybook 会根据组件函数的 TypeScript 类型自动应用最佳的 argType。如果手动覆盖,则每次组件类型更改时都必须同步 argType,因此若无正当理由,不要直接指定 argType。
需要手动指定的正当情况:
类型为 ReactNode 但需要在 Controls 中进行文本输入时 → control: 'text'
复合模式(导出多个组件)→ 通过 argTypes 明确指定
特定故事中必须始终固定的 prop → control: false
// ❌ 不必要的 argType 手动指定 — 类型更改时同步会中断 const meta = { component: Button, argTypes: { variant: { control: 'select', options: ['primary', 'secondary', 'tertiary'], }, size: { control: 'radio', options: ['sm', 'md', 'lg'], }, disabled: { control: 'boolean', }, }, } satisfies Meta<typeof Button>;
// ✅ 依赖自动推断,仅在必要时手动指定 const meta = { component: Button, argTypes: { // 类型为 ReactNode 但需要文本输入的情况 children: { control: 'text' }, }, } satisfies Meta<typeof Button>;
// ✅ 在特定故事中固定 prop 时 — control: false export const Horizontal: Story = { args: { orientation: 'horizontal' }, argTypes: { orientation: { control: false }, // 在这个故事中始终为 horizontal }, };
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
// 1. Meta 定义 — 省略 title,声明共用 args,argTypes 委托给自动推断
const meta = {
component: Button,
tags: ['autodocs'],
parameters: {
layout: 'centered',
},
args: {
children: 'Button',
size: 'md',
variant: 'primary',
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// 2. 基础故事 — 直接使用 Meta args
export const Primary: Story = {};
// 3. 变体故事 — 仅覆盖差异点
export const Secondary: Story = {
args: {
variant: 'secondary',
},
};
export const Disabled: Story = {
args: {
disabled: true,
},
};
// 4. 需要固定 prop 的故事 — 使用 control: false
export const Horizontal: Story = {
args: { orientation: 'horizontal' },
argTypes: {
orientation: { control: false },
},
};
// 5. 需要复杂状态或上下文的情况
export const WithCustomTheme: Story = {
decorators: [
(Story) => (
<ThemeProvider theme="custom">
<Story />
</ThemeProvider>
),
],
};
原则: 大多数 argType 由 Storybook 根据组件类型自动推断。以下仅在自动推断不适用时使用。
默认值在
args中声明,而不是argTypes.defaultValue。
argTypes: {
// 类型为 ReactNode 但需要文本输入时
children: { control: 'text' },
// Range slider (自动推断不适用时)
opacity: {
control: { type: 'range', min: 0, max: 1, step: 0.1 },
},
// Action logger (事件处理器)
onClick: { action: 'clicked' },
// 禁用 Control (在特定故事中固定 prop)
orientation: { control: false },
}
parameters: {
// 布局设置
layout: 'centered' | 'fullscreen' | 'padded',
// 背景设置
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#333333' },
],
},
// Actions 面板设置
actions: {
argTypesRegex: '^on[A-Z].*', // 自动检测以 on 开头的 Props
},
// Docs 设置
docs: {
description: {
component: '按钮组件详细说明',
},
},
}
// 1. 样式包装器
(Story) => (
<div style={{ padding: '3rem' }}>
<Story />
</div>
)
// 2. Theme Provider
(Story) => (
<ThemeProvider theme="dark">
<Story />
</ThemeProvider>
)
// 3. Router Provider (使用 React Router 时)
(Story) => (
<MemoryRouter initialEntries={['/']}>
<Story />
</MemoryRouter>
)
// 4. 多语言 Provider
(Story) => (
<I18nProvider locale="ko">
<Story />
</I18nProvider>
)
// 5. 全局状态 Provider
(Story) => (
<Provider store={mockStore}>
<Story />
</Provider>
)
Component.tsx # 组件实现
Component.stories.tsx # 故事文件 (同一目录)
Component.test.tsx # 测试文件
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(ts|tsx)'],
addons: [
'@storybook/addon-essentials', // Controls, Actions, Docs 等
'@storybook/addon-interactions', // Play functions
],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
// 应用于所有故事的全局 Decorators
decorators: [
(Story) => (
<div style={{ fontFamily: 'Arial, sans-serif' }}>
<Story />
</div>
),
],
};
export default preview;
每周安装量
264
代码仓库
GitHub 星标数
4
首次出现
2026年1月27日
安全审计
安装于
claude-code216
opencode214
codex212
github-copilot207
gemini-cli207
cursor189
최신 Component Story Format 3.0 사용. 더 간결하고 타입 안전.
// ❌ CSF 2.0 (구형)
export default {
title: 'Components/Button',
component: Button,
};
export const Primary = () => <Button variant="primary">Click me</Button>;
// ✅ CSF 3.0 (권장)
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta = {
component: Button,
tags: ['autodocs'], // 자동 문서 생성
args: {
variant: 'primary',
children: 'Click me',
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {};
export const Secondary: Story = {
args: {
variant: 'secondary',
},
};
컴포넌트 Props를 Args로 정의하여 Controls 패널에서 인터랙티브하게 조작 가능.
기본값은args에서 선언 (❌ argTypes.defaultValue 사용 금지). Meta의 args에 기본값을 두면 Controls 패널에서 자동으로 해당 값이 선택됨
여러 스토리에 공통으로 필요한 args는 Meta(컴포넌트) 수준에서 선언 하고, 개별 스토리에서는 차이점만 오버라이드
// ❌ 하드코딩된 Props export const Disabled: Story = { render: () => <Button disabled>Disabled</Button>, };
// ❌ 여러 스토리에서 같은 args 중복 export const Primary: Story = { args: { children: 'Click me', variant: 'primary' }, }; export const Secondary: Story = { args: { children: 'Click me', variant: 'secondary' }, };
// ✅ Meta에서 공통 args 선언, 스토리에서 차이점만 오버라이드 const meta = { component: Button, args: { children: 'Click me', variant: 'primary', }, } satisfies Meta<typeof Button>;
export const Primary: Story = {};
export const Secondary: Story = { args: { variant: 'secondary' }, };
export const Disabled: Story = { args: { disabled: true }, };
title을 문자열로 직접 명시하면 타입 안전하지 않고, 컴포넌트 이름/경로 변경 시 싱크가 깨지기 쉬움. Storybook은 파일 경로에서 사이드바 계층을 자동 추론하므로 title 생략.
// ❌ title 직접 명시 — 타입 안전하지 않고 싱크 깨짐 위험
const meta = {
title: 'Components/Button',
component: Button,
} satisfies Meta<typeof Button>;
// ✅ title 생략 — 파일 경로에서 자동 추론
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
satisfies 키워드로 타입 체크와 타입 추론 동시 활용.
// ❌ 타입 추론 불가
const meta: Meta<typeof Button> = {
component: Button,
};
// ✅ 타입 체크와 추론 모두 가능
const meta = {
component: Button,
args: {
size: 'md',
variant: 'primary',
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
공통 래퍼나 Provider를 Decorator로 적용.
// 개별 스토리에 Decorator 적용
export const WithTheme: Story = {
decorators: [
(Story) => (
<ThemeProvider theme="dark">
<Story />
</ThemeProvider>
),
],
};
// 모든 스토리에 Decorator 적용
const meta = {
component: Button,
decorators: [
(Story) => (
<div style={{ padding: '3rem' }}>
<Story />
</div>
),
],
} satisfies Meta<typeof Button>;
const meta = {
component: Button,
parameters: {
layout: 'centered', // 스토리를 중앙 정렬
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#000000' },
],
},
},
} satisfies Meta<typeof Button>;
// 개별 스토리에서 오버라이드
export const OnDark: Story = {
parameters: {
backgrounds: { default: 'dark' },
},
};
Storybook은 컴포넌트 함수의 TypeScript 타입에서 최적의 argType을 자동 적용함. 수동으로 덮어쓰면 컴포넌트 타입 변경 시마다 argType 싱크를 맞춰야 하므로 타당한 이유 없이 argType을 직접 지정하지 않음.
수동 지정이 타당한 경우:
ReactNode 타입인데 Controls에서 텍스트 입력이 필요할 때 → control: 'text'
Compound pattern (컴포넌트를 여러 개 export) → argTypes로 명시
특정 스토리에서 항상 고정되어야 하는 prop → control: false
// ❌ 불필요한 argType 수동 지정 — 타입 변경 시 싱크 깨짐 const meta = { component: Button, argTypes: { variant: { control: 'select', options: ['primary', 'secondary', 'tertiary'], }, size: { control: 'radio', options: ['sm', 'md', 'lg'], }, disabled: { control: 'boolean', }, }, } satisfies Meta<typeof Button>;
// ✅ 자동 추론에 맡기고, 필요한 경우만 수동 지정 const meta = { component: Button, argTypes: { // ReactNode 타입이지만 텍스트 입력이 필요한 경우 children: { control: 'text' }, }, } satisfies Meta<typeof Button>;
// ✅ 특정 스토리에서 prop을 고정할 때 — control: false export const Horizontal: Story = { args: { orientation: 'horizontal' }, argTypes: { orientation: { control: false }, // 이 스토리에서는 항상 horizontal }, };
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
// 1. Meta 정의 — title 생략, 공통 args 선언, argTypes는 자동 추론에 위임
const meta = {
component: Button,
tags: ['autodocs'],
parameters: {
layout: 'centered',
},
args: {
children: 'Button',
size: 'md',
variant: 'primary',
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// 2. 기본 스토리 — Meta args를 그대로 사용
export const Primary: Story = {};
// 3. 변형 스토리들 — 차이점만 오버라이드
export const Secondary: Story = {
args: {
variant: 'secondary',
},
};
export const Disabled: Story = {
args: {
disabled: true,
},
};
// 4. prop 고정이 필요한 스토리 — control: false 사용
export const Horizontal: Story = {
args: { orientation: 'horizontal' },
argTypes: {
orientation: { control: false },
},
};
// 5. 복잡한 상태나 컨텍스트가 필요한 경우
export const WithCustomTheme: Story = {
decorators: [
(Story) => (
<ThemeProvider theme="custom">
<Story />
</ThemeProvider>
),
],
};
원칙: 대부분의 argType은 Storybook이 컴포넌트 타입에서 자동 추론. 아래는 자동 추론이 부적절할 때만 사용.
기본값은
argTypes.defaultValue가 아닌args에서 선언.
argTypes: {
// ReactNode 타입이지만 텍스트 입력이 필요할 때
children: { control: 'text' },
// Range slider (자동 추론이 적절하지 않을 때)
opacity: {
control: { type: 'range', min: 0, max: 1, step: 0.1 },
},
// Action logger (이벤트 핸들러)
onClick: { action: 'clicked' },
// Control 비활성화 (특정 스토리에서 prop 고정)
orientation: { control: false },
}
parameters: {
// 레이아웃 설정
layout: 'centered' | 'fullscreen' | 'padded',
// 배경 설정
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#333333' },
],
},
// Actions 패널 설정
actions: {
argTypesRegex: '^on[A-Z].*', // on으로 시작하는 Props 자동 감지
},
// Docs 설정
docs: {
description: {
component: '버튼 컴포넌트 상세 설명',
},
},
}
// 1. 스타일 래퍼
(Story) => (
<div style={{ padding: '3rem' }}>
<Story />
</div>
)
// 2. Theme Provider
(Story) => (
<ThemeProvider theme="dark">
<Story />
</ThemeProvider>
)
// 3. Router Provider (React Router 사용 시)
(Story) => (
<MemoryRouter initialEntries={['/']}>
<Story />
</MemoryRouter>
)
// 4. 다국어 Provider
(Story) => (
<I18nProvider locale="ko">
<Story />
</I18nProvider>
)
// 5. 전역 상태 Provider
(Story) => (
<Provider store={mockStore}>
<Story />
</Provider>
)
Component.tsx # 컴포넌트 구현
Component.stories.tsx # 스토리 파일 (같은 디렉토리)
Component.test.tsx # 테스트 파일
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(ts|tsx)'],
addons: [
'@storybook/addon-essentials', // Controls, Actions, Docs 등
'@storybook/addon-interactions', // Play functions
],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
// 모든 스토리에 적용될 전역 Decorators
decorators: [
(Story) => (
<div style={{ fontFamily: 'Arial, sans-serif' }}>
<Story />
</div>
),
],
};
export default preview;
Weekly Installs
264
Repository
GitHub Stars
4
First Seen
Jan 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code216
opencode214
codex212
github-copilot207
gemini-cli207
cursor189
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装
竞争对手研究指南:SEO、内容、反向链接与定价分析工具
231 周安装
Azure 工作负载自动升级评估工具 - 支持 Functions、App Service 计划与 SKU 迁移
231 周安装
Kaizen持续改进方法论:软件开发中的渐进式优化与防错设计实践指南
231 周安装
软件UI/UX设计指南:以用户为中心的设计原则、WCAG可访问性与平台规范
231 周安装
Apify 网络爬虫和自动化平台 - 无需编码抓取亚马逊、谷歌、领英等网站数据
231 周安装
llama.cpp 中文指南:纯 C/C++ LLM 推理,CPU/非 NVIDIA 硬件优化部署
231 周安装