실무에서 쓰이는 디자인 시스템 — 스토리북: 테스트 주도 개발
안녕하세요. 오늘은 실무에서 쓰이는 디자인 시스템의 마지막 챕터인 ‘스토리북: 테스트 주도 개발’에 대해 이야기해 보겠습니다.

실무에서 쓰이는 디자인 시스템 — 스토리북: 테스트 주도 개발
안녕하세요. 오늘은 실무에서 쓰이는 디자인 시스템의 마지막 챕터인 ‘스토리북: 테스트 주도 개발’에 대해 이야기해 보겠습니다.
Chapter
- Introducing the Design System
- Design Tokens — Part 1
- Design Tokens — Part 2
- Storybook: TDD for Components
What is Storybook?
Storybook은 UI 개발 과정에서 테스트와 문서화를 지원하는 프론트엔드 도구로, 시각적인 테스트 주도 개발을 가능하게 합니다. 이를 통해 디자인 시스템 구축 시 QA 과정에서 기획자와 디자이너 등 이해관계자가 컴포넌트 단위로 UI를 쉽고 빠르게 테스트할 수 있습니다. 또한, Storybook 자체가 곧 문서 역할을 하므로, 문서를 효율적으로 작성하고 공유할 수 있습니다.
Getting Started
Storybook은 버전에 따라 차이가 클 수 있습니다. 이 글에서는 작성 시점 기준 최신 버전인 8.3.6을 기준으로, ‘React + Vite (TypeScript)’ 환경에서 설명합니다.
// Install
npx storybook@latest init
Exploring the Storybook UI
아래는 Storybook을 실행하면 볼 수 있는 Storybook UI 화면입니다. Storybook UI는 Sidebar, Toolbar, Canvas, Panels로 구성되어 있습니다.

Sidebar
Sidebar는 Storybook의 왼쪽 패널로, 프로젝트 내의 컴포넌트와 스토리 목록 등 계층 구조를 탐색할 수 있는 공간입니다. 이를 통해 사용자는 각 컴포넌트를 선택하고 해당 스토리를 쉽게 탐색할 수 있습니다. 프로젝트의 계층 구조에 대한 자세한 내용은 아래의 ‘Component Naming & Hierarchy’ 섹션에서 설명하겠습니다.
Toolbar
Toolbar는 Storybook의 상단에 위치하며, 컴포넌트 리마운트, 화면 확대/축소, 배경 색 변경, 그리드 등 다양한 기능을 제공합니다. 이 도구들을 통해 사용자는 컴포넌트의 UI를 쉽고 빠르게 테스트할 수 있습니다.
Canvas
Canvas는 Storybook의 중앙 영역으로, 선택한 스토리의 실제 렌더링 결과를 보여줍니다. 이곳에서 컴포넌트의 시각적 상태를 확인하고 사용자 상호작용을 테스트할 수 있습니다.
Panels
Panels는 Canvas 하단에 위치하며, Controls Tab을 통해 Props를 수정하고 즉각적인 결과를 관찰할 수 있습니다. 이외에도 이벤트 핸들러(콜백) 인수를 통해 수신된 데이터를 확인할 수 있는 Actions 등의 기능이 포함되어 있습니다.
Global Settings
Global Settings를 통해 웹폰트 및 전역 스타일 등 다양한 설정을 할 수 있습니다.
Preview
루트 위치의 .storybook 디렉토리 안에 있는 preview 파일의 확장자를 .ts에서 .tsx로 수정한 후, preview 객체 안에 아래 예시와 같이 decorators를 작성합니다. Storybook은 Story를 파라미터로 받아 빈 태그 안에 렌더링됩니다.
// preview.tsx
import type { Preview } from "@storybook/react";
import GlobalStyle from "../src/styles/common";
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
decorators: [
(Story) => (
<>
<GlobalStyle />
<Story />
</>
),
],
};
export default preview;
Preview Head
루트 위치의 .storybook 디렉토리 안에 preview-head.html 파일을 만들어 웹폰트 등의 CDN을 불러올 수 있습니다.
<!-- preview-head.html -->
<link
rel="stylesheet"
as="style"
crossorigin
href="someUrl"
/>
Story Files Overview
Stories 파일은 Storybook에서 컴포넌트의 상태를 정의하고 문서화하는 기본 단위입니다. 이 파일을 통해 컴포넌트의 다양한 상태를 시각적으로 테스트하고 문서화할 수 있습니다. 아래는 Stories 파일의 기본 구성 요소에 대한 설명입니다.
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '@/components/Button';
// Meta 데이터 정의
const meta = {
title: 'Design System/Components/Button',
component: Button,
tags: ["autodocs"],
} satisfies Meta<typeof Button>;
export default meta;
// Story 타입 정의
type Story = StoryObj<typeof meta>;
// Story 정의
export const Primary: Story = {
args: {
sort: "PRIMARY",
label: 'Button',
},
};
export const Secondary: Story = {
args: {
sort: "SECONDARY",
label: 'Button',
},
};
Meta 데이터 정의
- title은 Storybook UI에서 컴포넌트를 그룹화하는 데 사용됩니다. 계층 구조로 컴포넌트를 구성할 수 있으며, 위의 예시는 “Design System” 카테고리 내의 “Components” 폴더의 “Button” 컴포넌트를 나타냅니다. 계층 구조에 대해서는 아래에서 좀 더 자세히 설명하겠습니다.
- component는 해당 스토리가 연결된 실제 컴포넌트를 지정합니다.
- tags는 Storybook의 자동 문서화 기능을 활성화하기 위해 사용됩니다. “autodocs”는 자동 문서화를 의미하며, 컴포넌트의 Props 타입 정의 시 JSDoc을 통해 Description을 작성할 수 있습니다. Default는 컴포넌트의 Props의 기본 값에 따라 결정됩니다.
Story 타입 정의
StoryObj는 Storybook에서 제공하는 제너릭 타입으로, meta 객체의 구조와 속성을 기반으로 스토리 타입을 생성합니다.
Story 정의
각 스토리는 컴포넌트의 상태를 나타내며, args 속성을 사용하여 해당 컴포넌트에 Props를 전달합니다. 위의 예시에서는 Button 컴포넌트의 Primary와 Secondary 상태를 각각 스토리로 정의하고 있습니다.
Component Naming & Hierarchy
Storybook의 프로젝트 계층은 Category, Folder, Component로 구성됩니다. 이는 필수 사항은 아니며, 생성 가능한 계층 구조로 이해하셔도 좋습니다. 더 자세한 내용은 여기 링크를 통해 확인할 수 있습니다.

프로젝트 계층은 Stories Files 내의 meta 객체의 title을 통해 생성할 수 있으며, 슬래시(/)를 사용하여 계층을 구분합니다. 이때 서로 다른 Stories Files에서 title이 중복되면, 같은 컴포넌트로 인식되어 병합됩니다.
// Stories Files
const meta = {
title: 'Design System/Components/Button', // 계층 설정
tags: ["autodocs"], // Docs 설정
// ...some code
} satisfies Meta<typeof Button>;
export default meta;
// Story 생성
export const Primary: Story = {
// ...some code
};
Hierarchy Order
프로젝트 계층 순서는 “Global Settings” 섹션에서 설명한 preview 파일에서 설정할 수 있습니다. 아래 예시와 같이 preview 객체의 parameters 안에 options을 작성합니다. 예시에서는 Design System 카테고리 내의 폴더를 Pages, Components 순으로 정렬하고, Components 폴더의 첫 번째 컴포넌트를 Header로 지정하고 있습니다.
// preview.tsx
import type { Preview } from "@storybook/react";
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
// 프로젝트 계층 순서 설정
options: {
storySort: {
order: ["Design System", ["Pages", "Components", ["Header", "*"]]],
},
},
},
};
export default preview;
Specifying ArgTypes
Storybook에서 argTypes는 args에 대한 context를 제공합니다. 이를 통해 각 arg에 대해 명시적으로 name, description, control 등을 설정할 수 있습니다. 아래 예시 코드와 같이 argTypes를 해당 컴포넌트 전체 스토리에 설정할 수도 있고, 특정 스토리에 개별적으로 설정할 수도 있습니다. Control types에는 text, number, boolean, check, radio, select 등이 있으며, 더 많은 Control Types은 여기 링크에서 확인할 수 있습니다.
// 해당 컴포넌트 전체 스토리에 argTypes를 설정
const meta = {
argTypes: {
size: {
name: "customName",
description: "You can provide a description here",
control: { type: "select" },
},
} satisfies Meta<typeof Button>;
// 특정 스토리에 개별적으로 argTypes를 설정
export const Primary: Story = {
argTypes: {
size: {
name: "customName",
description: "You can provide a description here",
control: { type: "select" },
},
};
Storybook에서는 Control Panel에서 사용자에게 숨기고 싶은 arg가 있을 수 있습니다. 이럴 때, 아래 예시 코드처럼 argTypes의 table을 이용해 간단한 유틸 함수를 만들어 arg를 프라이빗하게 사용할 수 있습니다.
// 유틸 함수
export function disableProperty(name: string) {
return {
[name]: {
table: {
disable: true,
},
},
};
}
// 스토리 파일
const meta = {
argTypes: {
...disableProperty("size"),
} satisfies Meta<typeof Button>;
Custom Render Functions
스토리에서 Render 함수를 이용하면 스토리가 렌더링되는 방식을 추가로 제어할 수 있습니다. 이를 통해 스토리에서 컴포넌트를 감싸거나 useEffect, useState와 같은 훅을 사용할 수 있습니다. 더 자세한 내용은 여기 링크에서 확인하실 수 있습니다.
export const Tertiary: Story = {
args: {
// args...
},
render: function Render(args) {
const [state, setState] = useState();
useEffect(() => {
// some code...
}, []);
return <Button {...args} />;
},
};
Using useArgs
useArgs는 Storybook에서 제공하는 훅으로, 스토리의 args를 동적으로 관리하고 업데이트할 수 있게 해줍니다. 이를 통해 사용자 상호작용에 따라 스토리의 Props 값을 실시간으로 조정할 수 있습니다. useArgs의 동작 방식은 useState와 유사하다고 이해하셔도 좋습니다. 아래 예시와 같이 useArgs의 첫 번째 배열에서는 구조 분해 할당을 통해 isChecked라는 arg 값을 가져와 사용하고, 두 번째 배열에서는 updateArgs를 가져와 업데이트 함수로 사용합니다.
import { useArgs } from '@storybook/preview-api';
export const Tertiary: Story = {
args: {
// args...
},
render: function Render(args) {
const [{ isChecked }, updateArgs] = useArgs();
function onChange() {
updateArgs({ isChecked: !isChecked });
}
return <Checkbox {...args} onChange={onChange} isChecked={isChecked} />;
},
};
이것으로 ‘실무에서 쓰이는 디자인 시스템’ 시리즈를 마치도록 하겠습니다. 읽어주셔서 감사합니다.
참고
메타데이터
- post_id
- 46d94b5eb0ee
- slug
- 실무에서-쓰이는-디자인-시스템-스토리북-테스트-주도-개발-46d94b5eb0ee
- url
- https://medium.com/@duchanjo/%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EC%93%B0%EC%9D%B4%EB%8A%94-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B6%81-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C-46d94b5eb0ee
- canonical_url
- https://medium.com/@duchanjo/%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EC%93%B0%EC%9D%B4%EB%8A%94-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B6%81-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C-46d94b5eb0ee
- author_url
- https://medium.com/@duchanjo
- status
- ok
- fetched_at
- 2026-06-20 20:29:01