개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Storybook — UI 컴포넌트를 독립적으로 개발하고 문서화하는 도구

2026년 6월 10일
React컴포넌트문서화Storybook

Storybook은 컴포넌트를 애플리케이션과 완전히 분리된 환경에서 개발하고 테스트하는 도구입니다. 실제 페이지에 붙여야만 확인할 수 있었던 컴포넌트의 각 상태를 Story로 정의해두면, 디자이너·개발자·QA 모두가 브라우저에서 즉시 확인할 수 있는 살아있는 문서가 만들어집니다.

# 기존 프로젝트에 Storybook 설치 (Vite + React 기준 자동 감지)
npx storybook@latest init

# 개발 서버 실행
npm run storybook

핵심 개념

1. Story

Story는 컴포넌트의 특정 상태(렌더링 시나리오) 하나를 정의하는 단위입니다. 하나의 컴포넌트에 여러 Story를 작성해 primary, disabled, loading, error 같은 다양한 상태를 각각 문서화합니다.

Story 파일은 컴포넌트 파일과 나란히 두는 것이 관례입니다.

src/components/Button/
  Button.tsx
  Button.stories.tsx   ← Story 파일
// Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";

// 컴포넌트 수준 메타데이터
const meta: Meta<typeof Button> = {
  title: "Components/Button",   // Storybook 사이드바의 경로
  component: Button,
  tags: ["autodocs"],           // 자동 문서 페이지 생성
};

export default meta;

type Story = StoryObj<typeof Button>;

// 각 Story는 named export로 정의
export const Primary: Story = {
  args: {
    label: "클릭",
    variant: "primary",
    disabled: false,
  },
};

export const Secondary: Story = {
  args: {
    label: "취소",
    variant: "secondary",
  },
};

export const Disabled: Story = {
  args: {
    label: "비활성화",
    disabled: true,
  },
};

export const Loading: Story = {
  args: {
    label: "로딩 중",
    isLoading: true,
  },
};

export default에는 컴포넌트 수준 메타데이터를, named export에는 개별 Story를 정의합니다. 각 Story는 독립적으로 렌더링되므로 애플리케이션 전체 맥락 없이도 해당 상태를 즉시 확인할 수 있습니다.


2. Args

Args는 Story에 전달되는 props 데이터입니다. args에 정의한 값이 컴포넌트에 props로 전달되며, Storybook UI의 Controls 패널에서 실시간으로 수정할 수 있습니다.

Args는 세 단계로 상속됩니다.

// 1. 컴포넌트 수준 args (모든 Story에 공통 적용)
const meta: Meta<typeof Button> = {
  component: Button,
  args: {
    variant: "primary",  // 모든 Story의 기본값
  },
};

// 2. Story 수준 args (해당 Story에만 적용)
export const Disabled: Story = {
  args: {
    label: "비활성화",
    disabled: true,     // 이 Story에서만 disabled: true
  },
};

// 3. render 함수에서 args를 직접 사용
export const WithIcon: Story = {
  args: {
    label: "다운로드",
    icon: "download",
  },
  render: (args) => (
    <div style={{ padding: "1rem" }}>
      <Button {...args} />
    </div>
  ),
};

컴포넌트 수준 args → Story 수준 args 순서로 병합됩니다. 같은 키가 있으면 Story 수준이 덮어씁니다.

argTypes로 Args 메타데이터 정의:

const meta: Meta<typeof Button> = {
  component: Button,
  argTypes: {
    variant: {
      control: "select",
      options: ["primary", "secondary", "danger"],
      description: "버튼 스타일 변형",
    },
    onClick: {
      action: "clicked",  // Actions 패널에 클릭 이벤트 기록
    },
    size: {
      control: { type: "radio" },
      options: ["sm", "md", "lg"],
    },
  },
};

3. Controls

Controls는 Storybook UI 하단 패널에서 Args를 실시간으로 조작하는 인터페이스입니다. 코드를 수정하지 않고 props를 바꾸며 컴포넌트의 다양한 상태를 즉시 확인할 수 있습니다.

Storybook UI 구조

+------------------+---------------------------+
|                  |                           |
|  사이드바        |   컴포넌트 미리보기        |
|                  |                           |
|  - Components    |   [ Button: 클릭 ]        |
|    - Button      |                           |
|      Primary     |                           |
|      Secondary   |                           |
|      Disabled    |                           |
|                  |                           |
+------------------+---------------------------+
|  Controls  |  Actions  |  Docs               |
|                                              |
|  label     [ 클릭              ]            |
|  variant   [ primary    v ]                 |
|  disabled  [ ] false                        |
|  size      ( ) sm  (o) md  ( ) lg           |
+----------------------------------------------+

Controls 패널에서 변경한 값은 즉시 미리보기에 반영됩니다. 타입에 따라 Controls가 자동으로 적절한 입력 위젯을 선택합니다.

타입자동 생성 Controls
string텍스트 입력
boolean토글(체크박스)
number숫자 입력
string[] (options)셀렉트 또는 라디오
objectJSON 에디터

TypeScript를 사용하면 컴포넌트의 props 타입에서 Controls를 자동으로 추론합니다. argTypes로 특정 prop의 Controls 위젯을 직접 지정할 수도 있습니다.


4. Addon

Addon은 Storybook에 기능을 추가하는 플러그인입니다. @storybook/addon-essentials 패키지에는 가장 많이 쓰이는 Addon이 모두 포함돼 있으며 init 시 자동으로 설치됩니다.

@storybook/addon-essentials 포함 항목

  Controls   → Args를 실시간으로 조작
  Actions    → 이벤트 핸들러 호출 로깅
  Docs       → 자동 문서 페이지 생성
  Viewport   → 다양한 화면 크기 미리보기
  Backgrounds → 배경색 변경
  Toolbars   → 전역 Args 조작 도구
  Measure    → 컴포넌트 크기·여백 시각화
  Outline    → 모든 요소의 아웃라인 표시

개별 Addon을 별도로 설치하는 경우입니다.

# 접근성 검사 Addon
npm install -D @storybook/addon-a11y

# 인터랙션 테스트 Addon (play 함수)
npm install -D @storybook/addon-interactions
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
  stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "@storybook/addon-essentials",
    "@storybook/addon-a11y",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
};

export default config;

play 함수로 인터랙션 테스트:

@storybook/addon-interactions를 사용하면 Story 안에서 사용자 인터랙션을 자동 실행하는 play 함수를 정의할 수 있습니다.

import { userEvent, within } from "@storybook/test";

export const FilledForm: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByLabelText("이름"), "Alice");
    await userEvent.type(canvas.getByLabelText("이메일"), "alice@example.com");
    await userEvent.click(canvas.getByRole("button", { name: "제출" }));
  },
};

Storybook UI에서 해당 Story를 열면 play 함수가 자동 실행되며, 단계별 인터랙션 결과를 Interactions 패널에서 확인할 수 있습니다.


5. Docs

Docs는 컴포넌트별 자동 문서 페이지를 생성하는 기능입니다. tags: ["autodocs"]를 meta에 추가하면 해당 컴포넌트의 모든 Story, props 테이블, 설명이 한 페이지에 모입니다.

자동 문서화 예시:

// Button.stories.tsx
const meta: Meta<typeof Button> = {
  title: "Components/Button",
  component: Button,
  tags: ["autodocs"],   // 이 한 줄로 Docs 페이지 자동 생성
};

생성된 Docs 페이지에는 다음 내용이 자동으로 포함됩니다.

Docs 페이지 자동 생성 내용

  1. 컴포넌트 설명 (JSDoc 주석에서 추출)
  2. Props 테이블 (TypeScript 타입 또는 PropTypes에서 추출)
     - prop 이름, 타입, 기본값, 필수 여부, 설명
  3. 모든 Story 미리보기 (Controls 패널 포함)
  4. 각 Story의 소스 코드

JSDoc으로 설명 추가:

/**
 * 사용자가 액션을 수행하도록 유도하는 기본 버튼 컴포넌트입니다.
 * `variant`로 시각적 강조 수준을 조절합니다.
 */
export function Button({
  label,
  /** 버튼 스타일 변형. 기본값은 primary입니다. */
  variant = "primary",
  /** true이면 클릭이 비활성화됩니다. */
  disabled = false,
  onClick,
}: ButtonProps) {
  return (
    <button
      className={`btn btn--${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

JSDoc 주석이 그대로 Docs 페이지의 props 테이블 설명 컬럼에 표시됩니다. TypeScript 타입 정의에서 description을 별도로 작성하지 않아도 주석만으로 문서화가 완성됩니다.

MDX로 커스텀 문서 작성:

자동 생성 문서에 추가 설명이 필요하다면 MDX 형식으로 직접 문서를 작성할 수 있습니다.

{/* Button.mdx */}
import { Canvas, Controls, Meta } from "@storybook/blocks";
import * as ButtonStories from "./Button.stories";

<Meta of={ButtonStories} />

# Button

버튼은 사용자가 클릭해 특정 액션을 수행할 때 사용합니다.
`variant`에 따라 시각적 강조 수준이 달라집니다.

## 기본 사용법

<Canvas of={ButtonStories.Primary} />
<Controls of={ButtonStories.Primary} />

## 비활성화 상태

버튼이 클릭 불가능한 상태를 나타냅니다.

<Canvas of={ButtonStories.Disabled} />

MDX를 사용하면 자유로운 레이아웃과 추가 설명, 사용 가이드라인을 문서에 포함할 수 있습니다.

← 이전 글shadcn/ui — Radix UI와 Tailwind 기반 재사용 컴포넌트
다음 글 →Tailwind CSS — Utility First CSS 프레임워크