Storybook Complete Guide | UI Component Development Environment

Storybook Complete Guide | UI Component Development Environment

이 글의 핵심

Storybook is a frontend workshop for building UI components in isolation. It provides a sandbox to develop, test, and document components independently from your app.

Introduction

Storybook is an open-source tool for developing UI components in isolation. It allows you to build and test components outside your app, making development faster and more reliable.

Why Storybook?

Traditional development:

# Need to:
1. Start entire app
2. Navigate to component
3. Set up specific state
4. Test edge cases manually
5. Repeat for each component

With Storybook:

# Just:
1. Run Storybook
2. See all components
3. Test all states instantly
4. Document automatically

1. Installation

New Project

npx storybook@latest init

Storybook automatically detects your framework and configures itself!

Existing React Project

npx storybook@latest init

# Start Storybook
npm run storybook

2. Writing Stories

Basic Story

// Button.tsx
export interface ButtonProps {
  label: string;
  onClick?: () => void;
  variant?: 'primary' | 'secondary';
}

export function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {label}
    </button>
  );
}
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    label: 'Button',
    variant: 'primary',
  },
};

export const Secondary: Story = {
  args: {
    label: 'Button',
    variant: 'secondary',
  },
};

Interactive Stories

export const WithClick: Story = {
  args: {
    label: 'Click me',
    onClick: () => alert('Clicked!'),
  },
};

3. Component States

// UserCard.stories.tsx
import { UserCard } from './UserCard';

export default {
  title: 'Components/UserCard',
  component: UserCard,
};

export const Default = {
  args: {
    name: 'Alice',
    role: 'Developer',
    avatar: 'https://i.pravatar.cc/150?img=1',
  },
};

export const NoAvatar = {
  args: {
    name: 'Bob',
    role: 'Designer',
  },
};

export const LongName = {
  args: {
    name: 'Christopher Alexander Johnson',
    role: 'Senior Software Engineer',
  },
};

export const Loading = {
  args: {
    isLoading: true,
  },
};

export const Error = {
  args: {
    error: 'Failed to load user',
  },
};

4. Args and Controls

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'danger'],
    },
    size: {
      control: 'radio',
      options: ['small', 'medium', 'large'],
    },
    disabled: {
      control: 'boolean',
    },
    label: {
      control: 'text',
    },
  },
};

export default meta;

Now you can change args in UI!

5. Decorators

Global Decorator

// .storybook/preview.tsx
import { ThemeProvider } from '../src/theme';

export const decorators = [
  (Story) => (
    <ThemeProvider>
      <Story />
    </ThemeProvider>
  ),
];

Story-specific Decorator

export const Primary: Story = {
  args: { label: 'Button' },
  decorators: [
    (Story) => (
      <div style={{ padding: '3rem' }}>
        <Story />
      </div>
    ),
  ],
};

6. Addons

Essential Addons

npm install --save-dev @storybook/addon-essentials

Includes:

  • Docs - Auto-generated documentation
  • Controls - Interactive args editing
  • Actions - Event logging
  • Viewport - Responsive preview
  • Backgrounds - Test on different backgrounds
  • Toolbars - Custom toolbar items

a11y Addon

npm install --save-dev @storybook/addon-a11y
// .storybook/main.ts
export default {
  addons: ['@storybook/addon-a11y'],
};

Tests components for accessibility issues!

Interactions Addon

npm install --save-dev @storybook/addon-interactions
import { userEvent, within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';

export const WithInteraction: Story = {
  args: { label: 'Click me' },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    await userEvent.click(button);
    await expect(button).toHaveTextContent('Clicked!');
  },
};

7. Documentation

Auto-generated Docs

import type { Meta } from '@storybook/react';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'], // Enable auto docs
  parameters: {
    docs: {
      description: {
        component: 'A button component with multiple variants.',
      },
    },
  },
};

MDX Documentation

{/* Button.stories.mdx */}
import { Meta, Story, Canvas } from '@storybook/blocks';
import { Button } from './Button';

<Meta title="Components/Button" component={Button} />

# Button

A flexible button component.

## Usage

<Canvas>
  <Story name="Primary">
    <Button label="Click me" variant="primary" />
  </Story>
</Canvas>

## Props

- `label` - Button text
- `variant` - Button style (primary, secondary, danger)
- `onClick` - Click handler

8. Testing

Interaction Testing

export const LoginForm: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Fill form
    await userEvent.type(canvas.getByLabelText('Email'), '[email protected]');
    await userEvent.type(canvas.getByLabelText('Password'), 'password123');
    
    // Submit
    await userEvent.click(canvas.getByRole('button', { name: /submit/i }));
    
    // Assert
    await expect(canvas.getByText('Success!')).toBeInTheDocument();
  },
};

Visual Regression Testing

npm install --save-dev @storybook/test-runner chromatic
# Run tests
npm run test-storybook

# Visual testing with Chromatic
npx chromatic --project-token=<token>

9. Deployment

Build Static Storybook

npm run build-storybook

Deploy to GitHub Pages

# .github/workflows/storybook.yml
name: Deploy Storybook

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm run build-storybook
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./storybook-static

10. Best Practices

1. One Component Per Story File

components/
├── Button/
│   ├── Button.tsx
│   ├── Button.stories.tsx
│   ├── Button.test.tsx
│   └── Button.module.css

2. Cover All States

export const Loading: Story = { args: { isLoading: true } };
export const Error: Story = { args: { error: 'Failed' } };
export const Empty: Story = { args: { data: [] } };
export const WithData: Story = { args: { data: [...] } };

3. Use Args for Reusability

// Good: reusable
export const Primary: Story = {
  args: { variant: 'primary' }
};

// Bad: hardcoded
export const Primary: Story = {
  render: () => <Button variant="primary" />
};

Summary

Storybook transforms UI development:

  • Isolated development - build components independently
  • Interactive testing - test all states easily
  • Auto documentation - generate component docs
  • Visual testing - catch UI regressions
  • Team collaboration - share component library

Key Takeaways:

  1. Develop components in isolation
  2. Test all states and edge cases
  3. Auto-generate documentation
  4. Run interaction tests
  5. Deploy for team collaboration

Next Steps:

  • Test with Testing Library
  • Visual test with Chromatic
  • Build Design System

Resources: