
스토리북(Storybook)은 UI 컴포넌트를 독립적으로 개발하고, 문서화하며, 테스트할 수 있는 오픈 소스 도구다. 주로 React, Vue, Angular와 같은 다양한 프레임워크에서 사용되며, 개발자들에게 유용한 도구로 자리 잡고 있다. 스토리북을 사용하면 컴포넌트를 개별적으로 다룰 수 있어 전체 애플리케이션에 영향을 주지 않고도 컴포넌트 개발을 쉽게 진행할 수 있다.
컴포넌트 독립 개발: 스토리북을 사용하면 애플리케이션의 나머지 부분과 분리된 상태에서 컴포넌트를 개발할 수 있다. 이렇게 하면 각 컴포넌트를 별도로 개발하고 테스트할 수 있어, 애플리케이션의 복잡성을 줄이고 개발 속도를 높일 수 있다.
문서화: 스토리북은 자동으로 컴포넌트 문서를 생성할 수 있다. 특히 팀 내에서 매우 유용하. 각 컴포넌트의 사용법과 다양한 상태를 문서화하여 다른 개발자들이 쉽게 이해하고 사용할 수 있도록 도와준다.
테스트: 스토리북은 컴포넌트의 다양한 상태를 쉽게 테스트할 수 있게 해준다. 이를 통해 예상치 못한 버그를 사전에 발견하고 수정할 수 있다. 또한, 시각적 회귀 테스트와 상호작용 테스트도 지원한다.
애드온 확장: 스토리북은 다양한 애드온을 통해 기능을 확장할 수 있다. 예를 들어, Actions 애드온을 사용하면 컴포넌트의 이벤트를 쉽게 추적할 수 있고, Knobs 애드온을 사용하면 실시간으로 컴포넌트의 속성을 조작할 수 있다.
빌드 시간 증가: 스토리북을 사용하면 빌드 시간이 길어질 수 있다. 특히, 많은 스토리와 애드온을 사용하는 경우 빌드 및 로드 시간이 늘어날 수 있다.
버전 호환성 문제: 스토리북과 사용 중인 다른 라이브러리(예: Next.js) 간의 버전 호환성 문제가 발생할 수 있다. 이는 업데이트 시 주의해야 한다.
Airbnb
GitHub
Shopify
IBM
main.js
preview.js

파일의 물리적 위치에 따라 사이드바에서 스토리의 위치가 결정된다. 예를 들어, Button.stories.js 파일이 components/Button 폴더에 있으면, 스토리북은 이를 components/Button 카테고리 아래에 표시한다.
title 파라미터를 사용하여 스토리의 위치를 명시적으로 정의할 수 있다. 예를 들어, 다음과 같이 설정할 수 있다.
export default {
title: 'Design System/Atoms/Button',
component: Button,
};
스토리북과 테일윈드 CSS를 설치하고 설정해 본다. 여기서는 React 18, Next.js 14.2.4, Tailwind CSS 3.4.7 환경을 사용한다.
npx sb init
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
tailwind.config.js 파일 설정
module.exports = { content: [ "./src/**/*.{js,jsx,ts,tsx}", "./.storybook/**/*.{js,jsx,ts,tsx}" ], theme: { extend: {}, }, plugins: [], }
src/styles/globals.css 파일 생성 및 내용 추가
@tailwind base; @tailwind components; @tailwind utilities;
.storybook/preview.js 파일에 아래 코드 추가
import "../src/styles/globals.css";
npm run storybook
import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
// 스타일을 위한 기본 클래스 정의 (TailwindCSS 사용)
const baseClasses = "px-4 py-2 font-semibold transition duration-200 shadow";
// 사이즈 클래스 정의
const sizeClasses = {
small: "pt-1 pb-1 pr-3 pl-3 text-[13px]",
medium: "pt-1 pb-1 pr-4 pl-4 text-[15px]",
large: "pt-1 pb-1 pr-5 pl-5 text-[18px]",
};
// 색상 클래스 정의
const colorClasses = {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-500 text-white hover:bg-gray-600",
danger: "bg-red-500 text-white hover:bg-red-600",
};
// 모양 클래스 정의
const shapeClasses = {
rounded: "rounded-3xl",
square: "rounded",
};
export default function Button(props) {
const {
type = "button",
className = "",
onClick,
children,
size = "medium",
color = "primary",
shape = "rounded",
icon,
...rest
} = props;
const buttonClasses = classNames(
baseClasses,
sizeClasses[size],
colorClasses[color],
shapeClasses[shape],
className
);
return (
<button type={type} className={buttonClasses} onClick={onClick} {...rest}>
{icon && <span className="mr-2">{icon}</span>}
{children}
</button>
);
}
Button.propTypes = {
type: PropTypes.string,
className: PropTypes.string,
onClick: PropTypes.func,
children: PropTypes.node.isRequired,
size: PropTypes.oneOf(["small", "medium", "large"]),
color: PropTypes.oneOf(["primary", "secondary", "danger"]),
shape: PropTypes.oneOf(["rounded", "square"]),
icon: PropTypes.node,
};
스토리북에서 버튼 컴포넌트를 설명하기 위해 기본적인 메타데이터를 설정
export default {
title: "MyProject/Button",
component: Button,
};

Template 함수는 전달된 args를 사용하여 버튼 컴포넌트를 렌더링
const Template = (args) => <Button {...args} />;
각 스토리는 버튼 컴포넌트의 다양한 상태를 정의한다. Primary, Secondary, Danger, WithIcon과 같은 다양한 버튼 변형을 보여준다.
export const Primary = Template.bind({});
Primary.args = {
children: "Primary Button",
size: "medium",
color: "primary",
shape: "rounded",
onClick: action("clicked"),
};

import React from "react";
import { action } from "@storybook/addon-actions";
import Button from "@/components/Buttons/Button";
export default {
title: "MyProject/Button",
component: Button,
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
children: "Primary Button",
size: "medium",
color: "primary",
shape: "rounded",
onClick: action("clicked"),
};
export const Secondary = Template.bind({});
Secondary.args = {
children: "Secondary Button",
size: "small",
color: "secondary",
shape: "square",
onClick: action("clicked"),
};
export const Danger = Template.bind({});
Danger.args = {
children: "Danger Button",
size: "large",
color: "danger",
shape: "rounded",
onClick: action("clicked"),
};
export const WithIcon = Template.bind({});
WithIcon.args = {
children: "Button with Icon",
size: "medium",
color: "primary",
shape: "rounded",
onClick: action("clicked"),
icon: (
<span role="img" aria-label="icon">
🔥
</span>
),
};

스토리북 에드온은 스토리북의 기능을 확장하는 중요한 도구
npm install @storybook/addon-actions
.storybook/main.js 파일에 @storybook/addon-actions를 추가
module.exports = {
addons: ['@storybook/addon-actions'],
};
스토리 파일에서 action 함수를 사용하여 이벤트를 추적
import React from 'react';
import { action } from '@storybook/addon-actions';
import Button from '@/components/Buttons/Button';
export default {
title: 'Example/Button',
component: Button,
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
children: 'Primary Button',
onClick: action('clicked'),
};
npm install @storybook/addon-controls
.storybook/main.js 파일에 @storybook/addon-controls를 추가
module.exports = {
addons: ['@storybook/addon-controls'],
};
스토리 파일에서 argTypes을 사용하여 props를 정의
import React from 'react';
import Button from '@/components/Buttons/Button';
export default {
title: 'Example/Button',
component: Button,
argTypes: {
size: {
control: { type: 'select', options: ['small', 'medium', 'large'] },
},
color: {
control: { type: 'select', options: ['primary', 'secondary', 'danger'] },
},
},
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
children: 'Primary Button',
size: 'medium',
color: 'primary',
};
npm install @storybook/addon-docs
.storybook/main.js 파일에 @storybook/addon-docs를 추가
module.exports = {
addons: ['@storybook/addon-docs'],
};
스토리 파일에서 parameters를 사용하여 문서화 설정을 추가
import React from 'react';
import Button from '@/components/Buttons/Button';
export default {
title: 'Example/Button',
component: Button,
parameters: {
docs: {
description: {
component: 'Button 컴포넌트는 다양한 크기, 색상, 모양을 지원하는 기본적인 버튼입니다.',
},
},
},
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
children: 'Primary Button',
size: 'medium',
color: 'primary',
};
npm install @storybook/addon-viewport
.storybook/main.js 파일에 @storybook/addon-viewport를 추가
module.exports = {
addons: ['@storybook/addon-viewport'],
};
스토리 파일에서 parameters를 사용하여 Viewport 설정을 추가
import React from 'react';
import Button from '@/components/Buttons/Button';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
export default {
title: 'Example/Button',
component: Button,
parameters: {
viewport: {
viewports: INITIAL_VIEWPORTS,
},
},
};
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
children: 'Primary Button',
size: 'medium',
color: 'primary',
};
스토리북 공식 문서
테일윈드 CSS 공식 문서
이해관계자를 위한 문서 - Storybook Tutorials
컴포넌트와 스토리를 구성하는 모범 사례