storybook-stories
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStorybook Stories
Storybook Stories
This skill enforces consistent Storybook story creation patterns across the application. It ensures that all components have proper documentation, interactive examples, and follow the established project structure.
<critical_component_usage>
MANDATORY: Always Use Base UI Components from @compozy/ui
CRITICAL REQUIREMENTS:
- ✅ ALWAYS import components from package (
@compozy/ui)packages/ui - ✅ ALWAYS use existing base UI components instead of creating new ones from scratch
- ✅ ALWAYS follow design system rules from and
@.cursor/rules/react.mdc@.cursor/rules/shadcn.mdc - ✅ ALWAYS use design tokens (e.g., ,
bg-background,text-foreground) instead of explicit colorsborder-border - ❌ NEVER create components from scratch when a base component exists in
@compozy/ui - ❌ NEVER use explicit color values (e.g., ,
bg-white) - always use design tokenstext-black - ❌ NEVER duplicate component logic - compose from base components
Available Base Components:
All components from are available via :
packages/ui/src/components@compozy/ui- Button, Card, Dialog, Input, Select, Badge, Avatar, Accordion, Alert, etc.
- See for complete list of exports
packages/ui/src/index.ts
Design System Rules:
- Follow React best practices:
@.cursor/rules/react.mdc - Follow Shadcn UI patterns:
@.cursor/rules/shadcn.mdc - Use design tokens for theming: ,
bg-background,text-foreground, etc. </critical_component_usage>border-border
本技能用于在整个应用中强制推行统一的 Storybook story 创建规范,确保所有组件都有完善的文档、可交互示例,并且符合已确立的项目结构。
<critical_component_usage>
强制要求:始终使用来自 @compozy/ui 的基础 UI 组件
关键要求:
- ✅ 始终 从 包(
@compozy/ui)导入组件packages/ui - ✅ 始终 使用现有的基础 UI 组件,而非从零创建新组件
- ✅ 始终 遵循 和
@.cursor/rules/react.mdc中的设计系统规则@.cursor/rules/shadcn.mdc - ✅ 始终 使用设计令牌(例如 、
bg-background、text-foreground)而非直接写死颜色值border-border - ❌ 严禁 在 已存在对应基础组件的情况下从零创建新组件
@compozy/ui - ❌ 严禁 使用写死的颜色值(例如 、
bg-white)—— 始终使用设计令牌text-black - ❌ 严禁 重复编写组件逻辑—— 基于基础组件组合实现功能
可用的基础组件:
下的所有组件都可以通过 导入:
packages/ui/src/components@compozy/ui- Button、Card、Dialog、Input、Select、Badge、Avatar、Accordion、Alert 等
- 完整的导出列表可查看
packages/ui/src/index.ts
设计系统规则:
- 遵循 React 最佳实践:
@.cursor/rules/react.mdc - 遵循 Shadcn UI 模式:
@.cursor/rules/shadcn.mdc - 使用设计令牌实现主题化:、
bg-background、text-foreground等 </critical_component_usage>border-border
Instructions
使用说明
-
File Location & Naming
- Place story files in a folder within the same category folder as the component.
stories/ - Example: ->
src/components/base/accordion.tsx.src/components/base/stories/accordion.stories.tsx
- Place story files in a
-
Component Imports
- MANDATORY: Import base UI components from
@compozy/ui - Use:
import { Button, Card, Dialog } from "@compozy/ui"; - Only import custom/domain-specific components from local files
- Check to see available components before creating new ones
packages/ui/src/index.ts
- MANDATORY: Import base UI components from
-
Meta Configuration
- Title should follow the directory structure: or
components/custom/ComponentName.components/ui/ComponentName - Include in the meta object.
component - Set to
parameters.layoutby default."centered" - Add to describe the component.
parameters.docs.description.component - Use if the component requires a specific container width or context.
decorators - MANDATORY: Use explicit type annotation:
const meta: Meta<typeof Component> = { ... }
- Title should follow the directory structure:
-
Story Definition
- Define a helper type: .
type Story = StoryObj<typeof meta>; - Export stories as named constants (PascalCase).
- Always add JSDoc comments above each story export; these appear in the Storybook UI.
- Use the story as the primary example.
Default - MANDATORY: All stories must include property, even if empty:
argsargs: {} - Keep it concise: Create only essential stories (2-5 max per component). Avoid over-engineering with excessive variations or complex scenarios.
- Define a helper type:
-
Render vs Args
- Use functions for compound components (like Accordion, Dialog, Select) that require children composition.
render - Use for simple components (like Button, Badge) where props define the variation.
args - MANDATORY: Even when using , include
renderpropertyargs: {}
- Use
-
Design System Compliance
- ALWAYS use design tokens for colors: ,
bg-background,text-foregroundborder-border - NEVER use explicit colors: ,
bg-white,text-blackborder-gray-200 - Follow accessibility guidelines from
@.cursor/rules/shadcn.mdc - Use semantic HTML elements and proper ARIA attributes
- ALWAYS use design tokens for colors:
-
文件位置与命名
- 将 story 文件放在组件所属分类文件夹下的 目录中
stories/ - 示例:对应的 story 文件为
src/components/base/accordion.tsxsrc/components/base/stories/accordion.stories.tsx
- 将 story 文件放在组件所属分类文件夹下的
-
组件导入
- 强制要求:从 导入基础 UI 组件
@compozy/ui - 用法:
import { Button, Card, Dialog } from "@compozy/ui"; - 仅从本地文件导入自定义/业务专属组件
- 创建新组件前先检查 确认是否已有可用组件
packages/ui/src/index.ts
- 强制要求:从
-
Meta 配置
- 标题应遵循目录结构:或
components/custom/ComponentNamecomponents/ui/ComponentName - 在 meta 对象中包含 字段
component - 默认将 设置为
parameters.layout"centered" - 添加 字段描述组件用途
parameters.docs.description.component - 如果组件需要特定的容器宽度或上下文,可使用
decorators - 强制要求:使用显式类型注解:
const meta: Meta<typeof Component> = { ... }
- 标题应遵循目录结构:
-
Story 定义
- 定义辅助类型:
type Story = StoryObj<typeof meta>; - 将 stories 导出为 PascalCase 命名的常量
- 每个导出的 story 上方都要添加 JSDoc 注释,这些注释会显示在 Storybook UI 中
- 使用 story 作为主要示例
Default - 强制要求:所有 story 必须包含 属性,即使是空对象:
argsargs: {} - 保持简洁:仅创建核心必要的 story(每个组件最多 2-5 个),避免过度设计过多变体或复杂场景
- 定义辅助类型:
-
Render 与 Args 的使用
- 对于需要子元素组合的复合组件(如 Accordion、Dialog、Select)使用 函数
render - 对于通过 props 定义变体的简单组件(如 Button、Badge)使用
args - 强制要求:即使使用 ,也要包含
render属性args: {}
- 对于需要子元素组合的复合组件(如 Accordion、Dialog、Select)使用
-
设计系统合规要求
- 始终 使用设计令牌定义颜色:、
bg-background、text-foregroundborder-border - 严禁 使用写死的颜色:、
bg-white、text-blackborder-gray-200 - 遵循 中的无障碍指南
@.cursor/rules/shadcn.mdc - 使用语义化 HTML 元素和正确的 ARIA 属性
- 始终 使用设计令牌定义颜色:
Example Template
示例模板
Using Base UI Components from @compozy/ui
使用来自 @compozy/ui 的基础 UI 组件
tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button, Card, CardHeader, CardTitle, CardContent } from "@compozy/ui";
import { MyCustomComponent } from "./my-custom-component";
const meta: Meta<typeof MyCustomComponent> = {
title: "components/custom/MyCustomComponent",
component: MyCustomComponent,
parameters: {
layout: "centered",
docs: {
description: {
component: "A custom component that composes base UI components from @compozy/ui.",
},
},
},
// Optional decorator using design tokens
decorators: [
Story => (
<div className="w-[400px] p-4 bg-background border border-border rounded-lg">
<Story />
</div>
),
],
};
export default meta;
type Story = StoryObj<typeof meta>;
/**
* Default usage showing the standard behavior
* Uses base Button and Card components from @compozy/ui
*/
export const Default: Story = {
args: {},
render: () => (
<Card>
<CardHeader>
<CardTitle>My Custom Component</CardTitle>
</CardHeader>
<CardContent>
<MyCustomComponent>
<Button variant="default">Action</Button>
</MyCustomComponent>
</CardContent>
</Card>
),
};
/**
* Variation with specific props
* All styling uses design tokens (bg-background, text-foreground, etc.)
*/
export const WithVariant: Story = {
args: {},
render: () => (
<div className="bg-card border border-border rounded-lg p-4">
<MyCustomComponent variant="secondary">
<Button variant="outline">Secondary Action</Button>
</MyCustomComponent>
</div>
),
};tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button, Card, CardHeader, CardTitle, CardContent } from "@compozy/ui";
import { MyCustomComponent } from "./my-custom-component";
const meta: Meta<typeof MyCustomComponent> = {
title: "components/custom/MyCustomComponent",
component: MyCustomComponent,
parameters: {
layout: "centered",
docs: {
description: {
component: "A custom component that composes base UI components from @compozy/ui.",
},
},
},
// Optional decorator using design tokens
decorators: [
Story => (
<div className="w-[400px] p-4 bg-background border border-border rounded-lg">
<Story />
</div>
),
],
};
export default meta;
type Story = StoryObj<typeof meta>;
/**
* Default usage showing the standard behavior
* Uses base Button and Card components from @compozy/ui
*/
export const Default: Story = {
args: {},
render: () => (
<Card>
<CardHeader>
<CardTitle>My Custom Component</CardTitle>
</CardHeader>
<CardContent>
<MyCustomComponent>
<Button variant="default">Action</Button>
</MyCustomComponent>
</CardContent>
</Card>
),
};
/**
* Variation with specific props
* All styling uses design tokens (bg-background, text-foreground, etc.)
*/
export const WithVariant: Story = {
args: {},
render: () => (
<div className="bg-card border border-border rounded-lg p-4">
<MyCustomComponent variant="secondary">
<Button variant="outline">Secondary Action</Button>
</MyCustomComponent>
</div>
),
};Story for Base UI Component (from @compozy/ui)
基础 UI 组件的 Story(来自 @compozy/ui)
tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "@compozy/ui";
const meta: Meta<typeof Button> = {
title: "components/ui/Button",
component: Button,
parameters: {
layout: "centered",
docs: {
description: {
component: "A button component with multiple variants and sizes.",
},
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
/**
* Default button with standard styling
*/
export const Default: Story = {
args: {
children: "Button",
variant: "default",
size: "default",
},
};
/**
* All variants using design tokens
*/
export const AllVariants: Story = {
args: {},
render: () => (
<div className="flex flex-wrap gap-4 bg-background p-4 rounded-lg">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="muted">Muted</Button>
</div>
),
};tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "@compozy/ui";
const meta: Meta<typeof Button> = {
title: "components/ui/Button",
component: Button,
parameters: {
layout: "centered",
docs: {
description: {
component: "A button component with multiple variants and sizes.",
},
},
},
};
export default meta;
type Story = StoryObj<typeof meta>;
/**
* Default button with standard styling
*/
export const Default: Story = {
args: {
children: "Button",
variant: "default",
size: "default",
},
};
/**
* All variants using design tokens
*/
export const AllVariants: Story = {
args: {},
render: () => (
<div className="flex flex-wrap gap-4 bg-background p-4 rounded-lg">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="muted">Muted</Button>
</div>
),
};Best Practices
最佳实践
Conciseness & Simplicity
简洁性与简易性
<critical>
**MANDATORY: Keep Stories Concise and Focused**
- ✅ DO: Create only essential stories that demonstrate core functionality
- ✅ DO: Use minimal, realistic examples that show the component's purpose
- ✅ DO: Focus on one concept per story
- ❌ DON'T: Create excessive variations that don't add value
- ❌ DON'T: Over-engineer with complex mock data or elaborate scenarios
- ❌ DON'T: Create stories for every possible prop combination
- ❌ DON'T: Add unnecessary decorators or wrappers unless required
Story Count Guidelines:
- Simple components (Button, Badge): 2-3 stories max (Default + 1-2 key variations)
- Medium components (Card, Dialog): 3-4 stories max (Default + key use cases)
- Complex components: 4-5 stories max (Default + essential scenarios)
- Never create more than 5 stories per component unless absolutely necessary </critical>
<critical>
**强制要求:保持 Story 简洁聚焦**
- ✅ 应该:仅创建用于演示核心功能的必要 story
- ✅ 应该:使用精简、符合实际场景的示例来展示组件用途
- ✅ 应该:每个 story 只聚焦于一个功能点
- ❌ 不应该:创建大量无价值的冗余变体
- ❌ 不应该:使用复杂的 mock 数据或过于复杂的场景进行过度设计
- ❌ 不应该:为每一种可能的 prop 组合都创建 story
- ❌ 不应该:添加非必要的装饰器或包装层,除非确实需要
Story 数量指导原则:
- 简单组件(Button、Badge):最多 2-3 个 story(默认样式 + 1-2 个核心变体)
- 中等复杂度组件(Card、Dialog):最多 3-4 个 story(默认样式 + 核心使用场景)
- 复杂组件:最多 4-5 个 story(默认样式 + 必要场景)
- 除非绝对必要,每个组件的 story 数量永远不要超过 5 个 </critical>
Component Usage
组件使用
- Base Components First: Always check for existing components before creating new ones
@compozy/ui - Composition Over Creation: Compose complex components from base UI components
- Compound Components: Always demonstrate the full structure (Parent + Children) when using compound components from
@compozy/ui - Design Tokens: Always use design tokens (,
bg-background, etc.) instead of explicit colorstext-foreground
- 优先使用基础组件:创建新组件前始终先检查 是否已有对应组件
@compozy/ui - 组合优于创建:基于基础 UI 组件组合实现复杂组件
- 复合组件:使用来自 的复合组件时,始终演示完整结构(父组件 + 子组件)
@compozy/ui - 设计令牌:始终使用设计令牌(、
bg-background等)而非写死颜色值text-foreground
Story Structure
Story 结构
- Mock Data: Keep mock data minimal and realistic - only include what's necessary to demonstrate the component
- Interactivity: For components like Accordion or Dialog, ensure the function sets up the component in a way that allows interaction (e.g., not force-controlled unless necessary)
render - Cleanliness: Remove unused imports and generic "template" comments
- Type Safety: Always use explicit type annotation for :
metaconst meta: Meta<typeof Component> - Args Property: Always include in all stories, even when using custom
args: {}functionsrender - One Story Per Concept: Each story should demonstrate one clear use case or variation, not multiple concepts
- Mock 数据:保持 mock 数据精简且符合实际场景,仅包含演示组件所需的最少内容
- 交互性:对于 Accordion 或 Dialog 这类组件,确保 函数设置的组件支持交互(例如非强制受控模式,除非必要)
render - 整洁性:删除未使用的导入和通用模板注释
- 类型安全:始终为 使用显式类型注解:
metaconst meta: Meta<typeof Component> - Args 属性:所有 story 中始终包含 ,即使使用自定义
args: {}函数render - 每个 Story 对应一个概念:每个 story 应该只演示一个清晰的使用场景或变体,不要同时包含多个概念
Design System Compliance
设计系统合规
- Follow React Rules: Adhere to patterns in
@.cursor/rules/react.mdc - Follow Shadcn Rules: Adhere to patterns in
@.cursor/rules/shadcn.mdc - Accessibility: Use semantic HTML and proper ARIA attributes
- Theme Support: All stories should work in both light and dark themes using design tokens
- 遵循 React 规则:遵守 中的模式
@.cursor/rules/react.mdc - 遵循 Shadcn 规则:遵守 中的模式
@.cursor/rules/shadcn.mdc - 无障碍:使用语义化 HTML 和正确的 ARIA 属性
- 主题支持:所有 story 都应该通过设计令牌同时支持亮暗两种主题
Common Mistakes to Avoid
需要避免的常见错误
❌ Creating components from scratch when base components exist:
tsx
// ❌ BAD: Creating a button from scratch
export const Bad: Story = {
render: () => <button className="bg-blue-500 text-white px-4 py-2 rounded">Click me</button>,
};
// ✅ GOOD: Using base Button from @compozy/ui
import { Button } from "@compozy/ui";
export const Good: Story = {
args: {},
render: () => <Button variant="default">Click me</Button>,
};❌ Using explicit colors instead of design tokens:
tsx
// ❌ BAD: Using explicit colors
<div className="bg-white text-black border-gray-200">
// ✅ GOOD: Using design tokens
<div className="bg-background text-foreground border-border">❌ Missing args property:
tsx
// ❌ BAD: Missing args property
export const Bad: Story = {
render: () => <Button>Click</Button>,
};
// ✅ GOOD: Including args property
export const Good: Story = {
args: {},
render: () => <Button>Click</Button>,
};❌ Missing explicit type annotation:
tsx
// ❌ BAD: Type inference
const meta = {
title: "Components/Button",
component: Button,
} satisfies Meta<typeof Button>;
// ✅ GOOD: Explicit type annotation
const meta: Meta<typeof Button> = {
title: "Components/Button",
component: Button,
};❌ Over-engineering stories with unnecessary examples:
tsx
// ❌ BAD: Too many stories with similar variations
export const Default: Story = { ... };
export const WithIcon: Story = { ... };
export const WithIconLeft: Story = { ... };
export const WithIconRight: Story = { ... };
export const WithLongText: Story = { ... };
export const WithShortText: Story = { ... };
export const Disabled: Story = { ... };
export const Loading: Story = { ... };
export const WithTooltip: Story = { ... };
export const InCard: Story = { ... };
export const InDialog: Story = { ... };
// ... 10+ stories for a simple button
// ✅ GOOD: Concise, focused stories
export const Default: Story = {
args: {
children: "Button",
variant: "default",
},
};
export const Variants: Story = {
args: {},
render: () => (
<div className="flex gap-2">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
</div>
),
};
export const Disabled: Story = {
args: {
children: "Disabled",
disabled: true,
},
};
// Only 3 stories covering essential use cases❌ Over-complicated mock data or scenarios:
tsx
// ❌ BAD: Unnecessarily complex mock data
const mockUsers = [
{ id: "1", name: "John Doe", email: "john@example.com", role: "admin", avatar: "...", lastLogin: "...", permissions: [...], metadata: {...} },
{ id: "2", name: "Jane Smith", email: "jane@example.com", role: "user", avatar: "...", lastLogin: "...", permissions: [...], metadata: {...} },
// ... 10 more users with full data
];
// ✅ GOOD: Minimal, realistic data
const mockUsers = [
{ id: "1", name: "John Doe", email: "john@example.com" },
{ id: "2", name: "Jane Smith", email: "jane@example.com" },
];❌ 基础组件已存在的情况下从零创建组件:
tsx
// ❌ BAD: Creating a button from scratch
export const Bad: Story = {
render: () => <button className="bg-blue-500 text-white px-4 py-2 rounded">Click me</button>,
};
// ✅ GOOD: Using base Button from @compozy/ui
import { Button } from "@compozy/ui";
export const Good: Story = {
args: {},
render: () => <Button variant="default">Click me</Button>,
};❌ 使用写死的颜色而非设计令牌:
tsx
// ❌ BAD: Using explicit colors
<div className="bg-white text-black border-gray-200">
// ✅ GOOD: Using design tokens
<div className="bg-background text-foreground border-border">❌ 缺少 args 属性:
tsx
// ❌ BAD: Missing args property
export const Bad: Story = {
render: () => <Button>Click</Button>,
};
// ✅ GOOD: Including args property
export const Good: Story = {
args: {},
render: () => <Button>Click</Button>,
};❌ 缺少显式类型注解:
tsx
// ❌ BAD: Type inference
const meta = {
title: "Components/Button",
component: Button,
} satisfies Meta<typeof Button>;
// ✅ GOOD: Explicit type annotation
const meta: Meta<typeof Button> = {
title: "Components/Button",
component: Button,
};❌ 过度设计 story,添加不必要的示例:
tsx
// ❌ BAD: Too many stories with similar variations
export const Default: Story = { ... };
export const WithIcon: Story = { ... };
export const WithIconLeft: Story = { ... };
export const WithIconRight: Story = { ... };
export const WithLongText: Story = { ... };
export const WithShortText: Story = { ... };
export const Disabled: Story = { ... };
export const Loading: Story = { ... };
export const WithTooltip: Story = { ... };
export const InCard: Story = { ... };
export const InDialog: Story = { ... };
// ... 10+ stories for a simple button
// ✅ GOOD: Concise, focused stories
export const Default: Story = {
args: {
children: "Button",
variant: "default",
},
};
export const Variants: Story = {
args: {},
render: () => (
<div className="flex gap-2">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
</div>
),
};
export const Disabled: Story = {
args: {
children: "Disabled",
disabled: true,
},
};
// Only 3 stories covering essential use cases❌ 过于复杂的 mock 数据或场景:
tsx
// ❌ BAD: Unnecessarily complex mock data
const mockUsers = [
{ id: "1", name: "John Doe", email: "john@example.com", role: "admin", avatar: "...", lastLogin: "...", permissions: [...], metadata: {...} },
{ id: "2", name: "Jane Smith", email: "jane@example.com", role: "user", avatar: "...", lastLogin: "...", permissions: [...], metadata: {...} },
// ... 10 more users with full data
];
// ✅ GOOD: Minimal, realistic data
const mockUsers = [
{ id: "1", name: "John Doe", email: "john@example.com" },
{ id: "2", name: "Jane Smith", email: "jane@example.com" },
];