Storybook

DaleStudy/skills/skills/storybook

作者 DaleStudya11368b0a0596c2be86bdfd00e155515a9acf5afMIT4 个星标收录于 2026年10月9日更新于 2026年10月9日仓库6个月前更新

Storybook 스토리 작성 및 CSF 3.0 베스트 프랙티스 스킬. 다음 상황에서 사용: (1) 새 스토리 파일(.stories.tsx, .stories.ts) 작성 시, (2) 기존 스토리 수정 시, (3) Args, Decorators, Parameters 설정 시, (4) Storybook 설정 파일(.storybook/) 작업 시, (5) 'story', 'stories', 'storybook', 'CSF' 키워드가 포함된 작업 시

AI 生成的概览

指导按照 CSF 3.0 最佳实践编写 Storybook 故事与配置。

功能
该技能提供编写 Storybook 故事文件和配置文件的规范与代码示例。内容涵盖 CSF 3.0 格式、基于 args 的故事、类型安全的 Meta 定义、decorators、parameters、argTypes、文件命名以及 .storybook 配置文件。它输出的是指导与示例片段,而不是运行任何工具。
适用场景
在新建 .stories.tsx 或 .stories.ts 文件、修改现有故事、配置 args、decorators 或 parameters,或处理 .storybook 配置文件时使用。
运行要求
不附带脚本,仅为说明性内容。应用这些指导需要项目使用 Storybook 以及 React 和 TypeScript。

Storybook

모범 관례

1. CSF 3.0 형식 사용

최신 Component Story Format 3.0 사용. 더 간결하고 타입 안전.

tsx
// ❌ 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',  },};

2. Args 기반 스토리 작성

컴포넌트 Props를 Args로 정의하여 Controls 패널에서 인터랙티브하게 조작 가능.

  • 기본값은 args에서 선언 (❌ argTypes.defaultValue 사용 금지). Meta의 args에 기본값을 두면 Controls 패널에서 자동으로 해당 값이 선택됨
  • 여러 스토리에 공통으로 필요한 args는 Meta(컴포넌트) 수준에서 선언하고, 개별 스토리에서는 차이점만 오버라이드
tsx
// ❌ 하드코딩된 Propsexport 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 },};

3. title 생략 — 파일 경로 기반 자동 추론

title을 문자열로 직접 명시하면 타입 안전하지 않고, 컴포넌트 이름/경로 변경 시 싱크가 깨지기 쉬움. Storybook은 파일 경로에서 사이드바 계층을 자동 추론하므로 title 생략.

tsx
// ❌ title 직접 명시 — 타입 안전하지 않고 싱크 깨짐 위험const meta = {  title: 'Components/Button',  component: Button,} satisfies Meta<typeof Button>;
// ✅ title 생략 — 파일 경로에서 자동 추론const meta = {  component: Button,} satisfies Meta<typeof Button>;

4. 타입 안전한 Meta 정의

satisfies 키워드로 타입 체크와 타입 추론 동시 활용.

tsx
// ❌ 타입 추론 불가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>;

5. Decorators로 컨텍스트 제공

공통 래퍼나 Provider를 Decorator로 적용.

tsx
// 개별 스토리에 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>;

6. Parameters로 동작 커스터마이즈

tsx
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' },  },};

7. ArgTypes — 자동 추론 우선, 수동 지정 최소화

Storybook은 컴포넌트 함수의 TypeScript 타입에서 최적의 argType을 자동 적용함. 수동으로 덮어쓰면 컴포넌트 타입 변경 시마다 argType 싱크를 맞춰야 하므로 타당한 이유 없이 argType을 직접 지정하지 않음.

수동 지정이 타당한 경우:

  • ReactNode 타입인데 Controls에서 텍스트 입력이 필요할 때 → control: 'text'
  • Compound pattern (컴포넌트를 여러 개 export) → argTypes로 명시
  • 특정 스토리에서 항상 고정되어야 하는 prop → control: false
tsx
// ❌ 불필요한 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: falseexport const Horizontal: Story = {  args: { orientation: 'horizontal' },  argTypes: {    orientation: { control: false }, // 이 스토리에서는 항상 horizontal  },};

권장 스토리 구조

tsx
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>    ),  ],};

ArgTypes 수동 지정이 필요한 경우 참고

원칙: 대부분의 argType은 Storybook이 컴포넌트 타입에서 자동 추론. 아래는 자동 추론이 부적절할 때만 사용.

기본값은 argTypes.defaultValue가 아닌 args에서 선언.

tsx
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

tsx
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: '버튼 컴포넌트 상세 설명',    },  },}

Decorators 패턴

tsx
// 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 설정 파일

typescript
// .storybook/main.tsimport 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;
typescript
// .storybook/preview.tsimport 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;

来源与署名

来源:DaleStudy/skills位于skills/storybook提交a11368b

许可证: MIT

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架