Skip to content

Storybook Cheatsheet

Storybook - Build UIs in Isolation

Storybook is a frontend workshop for building UI components and pages in isolation. It helps you develop and share hard-to-reach states and edge cases without needing to run your whole app. Thousands of teams use it for UI development, testing, and documentation.

Table of Contents

Installation

Quick Start

# Initialize Storybook in existing project
npx storybook@latest init

# Create new project with Storybook
npx create-react-app my-app
cd my-app
npx storybook@latest init

# Start Storybook
npm run storybook

Manual Installation

# Install Storybook CLI
npm install -g @storybook/cli

# Initialize in project
sb init

# Or specify framework
sb init --type react
sb init --type vue3
sb init --type angular

Framework-Specific Setup

# React
npx storybook@latest init --type react

# Vue 3
npx storybook@latest init --type vue3

# Angular
npx storybook@latest init --type angular

# Svelte
npx storybook@latest init --type svelte

# Web Components
npx storybook@latest init --type web-components

# HTML
npx storybook@latest init --type html

Project Structure

my-app/
├── .storybook/
│   ├── main.js          # Main configuration
│   └── preview.js       # Global settings
├── src/
│   ├── components/
│   │   ├── Button/
│   │   │   ├── Button.js
│   │   │   ├── Button.css
│   │   │   └── Button.stories.js
│   │   └── Header/
│   │       ├── Header.js
│   │       └── Header.stories.js
│   └── stories/         # Example stories
├── package.json
└── README.md

Getting Started

Basic Story

// src/components/Button/Button.stories.js
import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

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

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

export const Large = {
  args: {
    size: 'large',
    label: 'Button',
  },
};

export const Small = {
  args: {
    size: 'small',
    label: 'Button',
  },
};

Component Example

// src/components/Button/Button.js
import React from 'react';
import PropTypes from 'prop-types';
import './button.css';

export const Button = ({ primary = false, backgroundColor = null, size = 'medium', label, ...props }) => {
  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
  return (
    <button
      type="button"
      className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
      style={{ backgroundColor }}
      {...props}
    >
      {label}
    </button>
  );
};

Button.propTypes = {
  primary: PropTypes.bool,
  backgroundColor: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  label: PropTypes.string.isRequired,
  onClick: PropTypes.func,
};

Button.defaultProps = {
  primary: false,
  onClick: undefined,
};

Running Storybook

# Start development server
npm run storybook

# Build static Storybook
npm run build-storybook

# Serve built Storybook
npx http-server storybook-static

# Start with specific port
npm run storybook -- --port 9001

# Start in quiet mode
npm run storybook -- --quiet

Writing Stories

Story Structure

// Component.stories.js
import { Component } from './Component';

// Default export - story metadata
export default {
  title: 'Path/To/Component',
  component: Component,
  parameters: {
    // Story-level parameters
  },
  argTypes: {
    // Control definitions
  },
  args: {
    // Default args for all stories
  },
};

// Named exports - individual stories
export const Default = {};

export const WithProps = {
  args: {
    prop1: 'value1',
    prop2: 'value2',
  },
};

export const CustomStory = {
  render: (args) => <Component {...args} />,
  args: {
    // Story-specific args
  },
};

Story Naming

// Organize stories with hierarchy
export default {
  title: 'Design System/Components/Button',
  // Creates: Design System > Components > Button
};

export default {
  title: 'Pages/Dashboard/UserProfile',
  // Creates: Pages > Dashboard > UserProfile
};

export default {
  title: 'Example/Button',
  // Creates: Example > Button
};

Multiple Components

// stories/Forms.stories.js
import { Input } from '../components/Input';
import { Button } from '../components/Button';
import { Form } from '../components/Form';

export default {
  title: 'Forms/LoginForm',
};

export const LoginForm = () => (
  <Form>
    <Input type="email" placeholder="Email" />
    <Input type="password" placeholder="Password" />
    <Button primary>Login</Button>
  </Form>
);

export const SignupForm = () => (
  <Form>
    <Input placeholder="Full Name" />
    <Input type="email" placeholder="Email" />
    <Input type="password" placeholder="Password" />
    <Input type="password" placeholder="Confirm Password" />
    <Button primary>Sign Up</Button>
  </Form>
);

Story Parameters

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    // Layout
    layout: 'centered', // 'centered' | 'fullscreen' | 'padded'

    // Backgrounds
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#333333' },
      ],
    },

    // Viewport
    viewport: {
      defaultViewport: 'mobile1',
    },

    // Documentation
    docs: {
      description: {
        component: 'Button component for user interactions',
      },
    },
  },
};

Story Formats

CSF 3.0 (Current)

// Modern format with object syntax
export default {
  title: 'Example/Button',
  component: Button,
};

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

export const Secondary = {
  args: {
    primary: false,
    label: 'Button',
  },
};

CSF 2.0 (Legacy)

// Function-based format
export default {
  title: 'Example/Button',
  component: Button,
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  primary: false,
  label: 'Button',
};

Custom Render Functions

export const CustomRender = {
  render: (args) => {
    const [count, setCount] = React.useState(0);

    return (
      <div>
        <p>Count: {count}</p>
        <Button 
          {...args} 
          onClick={() => setCount(count + 1)}
        />
      </div>
    );
  },
  args: {
    label: 'Increment',
  },
};

Play Functions

import { userEvent, within } from '@storybook/testing-library';

export const InteractiveExample = {
  args: {
    label: 'Click me',
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');

    // Simulate user interaction
    await userEvent.click(button);

    // Add assertions if needed
    // expect(button).toHaveClass('clicked');
  },
};

Controls & Actions

ArgTypes Configuration

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    // Text input
    label: {
      control: 'text',
      description: 'Button label text',
    },

    // Boolean
    primary: {
      control: 'boolean',
      description: 'Primary button style',
    },

    // Select dropdown
    size: {
      control: 'select',
      options: ['small', 'medium', 'large'],
      description: 'Button size',
    },

    // Radio buttons
    variant: {
      control: 'radio',
      options: ['primary', 'secondary', 'danger'],
    },

    // Color picker
    backgroundColor: {
      control: 'color',
    },

    // Number input
    borderRadius: {
      control: { type: 'number', min: 0, max: 50, step: 1 },
    },

    // Range slider
    opacity: {
      control: { type: 'range', min: 0, max: 1, step: 0.1 },
    },

    // Object editor
    style: {
      control: 'object',
    },

    // Array editor
    items: {
      control: 'object',
    },

    // Date picker
    createdAt: {
      control: 'date',
    },

    // Disable control
    onClick: {
      action: 'clicked',
      control: false,
    },
  },
};

Actions

import { action } from '@storybook/addon-actions';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    onClick: { action: 'clicked' },
    onMouseEnter: { action: 'mouse-enter' },
    onMouseLeave: { action: 'mouse-leave' },
  },
};

// Or in individual stories
export const WithActions = {
  args: {
    label: 'Button',
    onClick: action('button-click'),
    onMouseEnter: action('mouse-enter'),
  },
};

// Multiple actions
export const MultipleActions = {
  args: {
    label: 'Multi Action Button',
    onClick: (...args) => {
      action('primary-click')(...args);
      action('analytics-track')('button-clicked');
    },
  },
};

Advanced Controls

export default {
  title: 'Example/Form',
  component: Form,
  argTypes: {
    // Conditional controls
    showAdvanced: {
      control: 'boolean',
    },
    advancedOptions: {
      control: 'object',
      if: { arg: 'showAdvanced', truthy: true },
    },

    // Custom control
    theme: {
      control: {
        type: 'select',
        labels: {
          light: 'Light Theme',
          dark: 'Dark Theme',
          auto: 'Auto (System)',
        },
      },
      options: ['light', 'dark', 'auto'],
    },

    // Multi-select
    features: {
      control: 'multi-select',
      options: ['feature1', 'feature2', 'feature3'],
    },
  },
};

Addons

Essential Addons

// .storybook/main.js
module.exports = {
  addons: [
    '@storybook/addon-essentials', // Includes most common addons
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
    '@storybook/addon-design-tokens',
  ],
};

// Individual addons (if not using essentials)
module.exports = {
  addons: [
    '@storybook/addon-controls',
    '@storybook/addon-actions',
    '@storybook/addon-viewport',
    '@storybook/addon-backgrounds',
    '@storybook/addon-docs',
    '@storybook/addon-toolbars',
  ],
};

Accessibility Addon

// .storybook/preview.js
export const parameters = {
  a11y: {
    // Optional selector to inspect
    element: '#storybook-root',
    config: {
      rules: [
        {
          // Disable specific rules
          id: 'color-contrast',
          enabled: false,
        },
      ],
    },
    // Show violations in action panel
    manual: true,
  },
};

// In stories
export const AccessibleButton = {
  args: {
    label: 'Accessible Button',
  },
  parameters: {
    a11y: {
      // Story-specific a11y config
      config: {
        rules: [
          { id: 'button-name', enabled: true },
        ],
      },
    },
  },
};

Viewport Addon

// .storybook/preview.js
export const parameters = {
  viewport: {
    viewports: {
      mobile: {
        name: 'Mobile',
        styles: {
          width: '375px',
          height: '667px',
        },
      },
      tablet: {
        name: 'Tablet',
        styles: {
          width: '768px',
          height: '1024px',
        },
      },
      desktop: {
        name: 'Desktop',
        styles: {
          width: '1024px',
          height: '768px',
        },
      },
    },
    defaultViewport: 'mobile',
  },
};

Design Tokens Addon

// design-tokens.json
{
  "colors": {
    "primary": "#007bff",
    "secondary": "#6c757d",
    "success": "#28a745",
    "danger": "#dc3545"
  },
  "spacing": {
    "xs": "4px",
    "sm": "8px",
    "md": "16px",
    "lg": "24px",
    "xl": "32px"
  },
  "typography": {
    "fontFamily": {
      "primary": "Inter, sans-serif",
      "mono": "Monaco, monospace"
    },
    "fontSize": {
      "sm": "14px",
      "md": "16px",
      "lg": "18px",
      "xl": "24px"
    }
  }
}

// .storybook/main.js
module.exports = {
  addons: [
    {
      name: '@storybook/addon-design-tokens',
      options: {
        designTokenGlob: '**/design-tokens.json',
      },
    },
  ],
};

Custom Addons

// .storybook/addons/theme-switcher/register.js
import { addons, types } from '@storybook/addons';
import { ADDON_ID, TOOL_ID } from './constants';
import { Tool } from './Tool';

addons.register(ADDON_ID, () => {
  addons.add(TOOL_ID, {
    type: types.TOOL,
    title: 'Theme Switcher',
    render: Tool,
  });
});

// .storybook/addons/theme-switcher/Tool.js
import React from 'react';
import { useGlobals } from '@storybook/api';
import { IconButton } from '@storybook/components';

export const Tool = () => {
  const [globals, updateGlobals] = useGlobals();
  const theme = globals.theme || 'light';

  const toggleTheme = () => {
    updateGlobals({
      theme: theme === 'light' ? 'dark' : 'light',
    });
  };

  return (
    <IconButton
      key="theme-switcher"
      title="Toggle theme"
      onClick={toggleTheme}
    >
      {theme === 'light' ? '🌙' : '☀️'}
    </IconButton>
  );
};

Configuration

Main Configuration

// .storybook/main.js
module.exports = {
  // Stories location
  stories: [
    '../src/**/*.stories.@(js|jsx|ts|tsx|mdx)',
    '../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
  ],

  // Addons
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
  ],

  // Framework
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },

  // Features
  features: {
    buildStoriesJson: true,
    storyStoreV7: true,
  },

  // TypeScript
  typescript: {
    check: false,
    reactDocgen: 'react-docgen-typescript',
    reactDocgenTypescriptOptions: {
      shouldExtractLiteralValuesFromEnum: true,
      propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
    },
  },

  // Webpack customization
  webpackFinal: async (config) => {
    // Add custom webpack config
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
    });

    return config;
  },

  // Vite customization
  viteFinal: async (config) => {
    // Customize Vite config
    config.define = {
      ...config.define,
      global: 'globalThis',
    };

    return config;
  },

  // Documentation
  docs: {
    autodocs: 'tag',
  },

  // Static directories
  staticDirs: ['../public'],
};

Preview Configuration

// .storybook/preview.js
import '../src/index.css'; // Global styles

export const parameters = {
  // Actions
  actions: { argTypesRegex: '^on[A-Z].*' },

  // Controls
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
    expanded: true,
    sort: 'requiredFirst',
  },

  // Layout
  layout: 'centered',

  // Backgrounds
  backgrounds: {
    default: 'light',
    values: [
      { name: 'light', value: '#ffffff' },
      { name: 'dark', value: '#333333' },
      { name: 'gray', value: '#f8f9fa' },
    ],
  },

  // Viewport
  viewport: {
    viewports: {
      mobile1: {
        name: 'Small mobile',
        styles: { width: '320px', height: '568px' },
      },
      mobile2: {
        name: 'Large mobile',
        styles: { width: '414px', height: '896px' },
      },
      tablet: {
        name: 'Tablet',
        styles: { width: '768px', height: '1024px' },
      },
    },
  },

  // Documentation
  docs: {
    theme: themes.light,
    source: {
      state: 'open',
    },
  },
};

// Global decorators
export const decorators = [
  (Story) => (
    <div style={{ margin: '3em', fontFamily: 'Inter, sans-serif' }}>
      <Story />
    </div>
  ),
];

// Global args
export const args = {
  // Default args for all stories
};

// Global arg types
export const argTypes = {
  // Global arg types
};

Environment Variables

// .storybook/main.js
module.exports = {
  env: (config) => ({
    ...config,
    API_URL: 'https://api.example.com',
    FEATURE_FLAG: true,
  }),

  webpackFinal: async (config) => {
    config.plugins.push(
      new webpack.DefinePlugin({
        'process.env.STORYBOOK': JSON.stringify(true),
      })
    );
    return config;
  },
};

// In components
const apiUrl = process.env.STORYBOOK 
  ? 'https://api-mock.example.com' 
  : process.env.API_URL;

Theming

Custom Theme

// .storybook/theme.js
import { create } from '@storybook/theming';

export default create({
  base: 'light', // 'light' | 'dark'

  // Brand
  brandTitle: 'My Company Storybook',
  brandUrl: 'https://example.com',
  brandImage: 'https://example.com/logo.svg',
  brandTarget: '_self',

  // Colors
  colorPrimary: '#FF4785',
  colorSecondary: '#029CFD',

  // UI
  appBg: '#F6F9FC',
  appContentBg: '#FFFFFF',
  appBorderColor: '#E6ECF0',
  appBorderRadius: 4,

  // Typography
  fontBase: '"Inter", sans-serif',
  fontCode: 'Monaco, monospace',

  // Text colors
  textColor: '#2E3438',
  textInverseColor: '#FFFFFF',
  textMutedColor: '#798186',

  // Toolbar
  barTextColor: '#798186',
  barSelectedColor: '#029CFD',
  barBg: '#FFFFFF',

  // Form
  inputBg: '#FFFFFF',
  inputBorder: '#E6ECF0',
  inputTextColor: '#2E3438',
  inputBorderRadius: 4,
});

// .storybook/manager.js
import { addons } from '@storybook/addons';
import theme from './theme';

addons.setConfig({
  theme,
});

Dark Theme

// .storybook/dark-theme.js
import { create } from '@storybook/theming';

export default create({
  base: 'dark',

  brandTitle: 'My Company Storybook',
  brandUrl: 'https://example.com',
  brandImage: 'https://example.com/logo-white.svg',

  colorPrimary: '#FF4785',
  colorSecondary: '#029CFD',

  appBg: '#1A1A1A',
  appContentBg: '#2D2D2D',
  appBorderColor: '#404040',

  textColor: '#FFFFFF',
  textInverseColor: '#1A1A1A',
  textMutedColor: '#CCCCCC',

  barTextColor: '#CCCCCC',
  barSelectedColor: '#029CFD',
  barBg: '#2D2D2D',

  inputBg: '#404040',
  inputBorder: '#666666',
  inputTextColor: '#FFFFFF',
});

Theme Switching

// .storybook/preview.js
import { themes } from '@storybook/theming';

export const parameters = {
  docs: {
    theme: themes.light,
  },
  darkMode: {
    // Override the default dark theme
    dark: { ...themes.dark, appBg: 'black' },
    // Override the default light theme
    light: { ...themes.normal, appBg: 'white' },
    // Set the initial theme
    current: 'light',
    // Disable the addon for specific stories
    stylePreview: true,
  },
};

// Global decorator for theme switching
export const decorators = [
  (Story, context) => {
    const theme = context.globals.theme || 'light';

    return (
      <div className={`theme-${theme}`}>
        <Story />
      </div>
    );
  },
];

// Global types for theme switcher
export const globalTypes = {
  theme: {
    name: 'Theme',
    description: 'Global theme for components',
    defaultValue: 'light',
    toolbar: {
      icon: 'circlehollow',
      items: [
        { value: 'light', icon: 'sun', title: 'Light' },
        { value: 'dark', icon: 'moon', title: 'Dark' },
      ],
      showName: true,
    },
  },
};

Testing

Visual Testing

// .storybook/test-runner.js
const { getStoryContext } = require('@storybook/test-runner');

module.exports = {
  async postRender(page, context) {
    const storyContext = await getStoryContext(page, context);

    // Skip visual tests for specific stories
    if (storyContext.parameters?.skipVisualTest) {
      return;
    }

    // Take screenshot
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      customSnapshotIdentifier: context.id,
    });
  },
};

// In stories
export const VisualTest = {
  args: {
    label: 'Visual Test Button',
  },
  parameters: {
    // Skip this story in visual tests
    skipVisualTest: true,
  },
};

Interaction Testing

// Button.stories.js
import { userEvent, within, expect } from '@storybook/test';

export const InteractionTest = {
  args: {
    label: 'Click me',
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);

    // Find elements
    const button = canvas.getByRole('button', { name: /click me/i });

    // Simulate interactions
    await userEvent.click(button);

    // Assertions
    await expect(args.onClick).toHaveBeenCalled();

    // Check DOM changes
    await expect(button).toHaveClass('clicked');

    // Wait for async operations
    await canvas.findByText('Success!');
  },
};

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

    // Fill form
    const emailInput = canvas.getByLabelText(/email/i);
    const passwordInput = canvas.getByLabelText(/password/i);
    const submitButton = canvas.getByRole('button', { name: /submit/i });

    await userEvent.type(emailInput, 'user@example.com');
    await userEvent.type(passwordInput, 'password123');
    await userEvent.click(submitButton);

    // Check results
    await expect(canvas.getByText(/success/i)).toBeInTheDocument();
  },
};

Unit Testing with Stories

// Button.test.js
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';

const { Primary, Secondary, Large } = composeStories(stories);

describe('Button', () => {
  test('renders primary button', () => {
    render(<Primary />);
    expect(screen.getByRole('button')).toHaveClass('storybook-button--primary');
  });

  test('renders secondary button', () => {
    render(<Secondary />);
    expect(screen.getByRole('button')).not.toHaveClass('storybook-button--primary');
  });

  test('renders large button', () => {
    render(<Large />);
    expect(screen.getByRole('button')).toHaveClass('storybook-button--large');
  });
});

Accessibility Testing

// a11y.test.js
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';

expect.extend(toHaveNoViolations);

const { Primary, Secondary } = composeStories(stories);

describe('Button Accessibility', () => {
  test('Primary button should not have accessibility violations', async () => {
    const { container } = render(<Primary />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });

  test('Secondary button should not have accessibility violations', async () => {
    const { container } = render(<Secondary />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});

Documentation

Auto Documentation

// Component with JSDoc
/**
 * Primary UI component for user interaction
 */
export const Button = ({
  /**
   * Is this the principal call to action on the page?
   */
  primary = false,
  /**
   * What background color to use
   */
  backgroundColor,
  /**
   * How large should the button be?
   */
  size = 'medium',
  /**
   * Button contents
   */
  label,
  /**
   * Optional click handler
   */
  onClick,
  ...props
}) => {
  // Component implementation
};

// Stories with auto-generated docs
export default {
  title: 'Example/Button',
  component: Button,
  tags: ['autodocs'], // Enable auto-documentation
  parameters: {
    docs: {
      description: {
        component: 'Button component for user interactions. Supports multiple sizes and variants.',
      },
    },
  },
};

Custom Documentation

// Button.stories.js
export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    docs: {
      description: {
        component: `
# Button Component

The Button component is a fundamental UI element used throughout the application.

## Usage

\`\`\`jsx
import { Button } from './Button';

<Button primary onClick={handleClick}>
  Click me
</Button>
\`\`\`

## Design Guidelines

- Use primary buttons for main actions
- Use secondary buttons for supporting actions
- Ensure sufficient color contrast for accessibility
        `,
      },
    },
  },
};

export const Primary = {
  args: {
    primary: true,
    label: 'Button',
  },
  parameters: {
    docs: {
      description: {
        story: 'Primary buttons are used for the main call-to-action on a page.',
      },
    },
  },
};

MDX Documentation

<!-- Button.stories.mdx -->
import { Meta, Story, Canvas, ArgsTable, Description } from '@storybook/addon-docs';
import { Button } from './Button';

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

# Button

<Description of={Button} />

## Examples

### Primary Button

<Canvas>
  <Story name="Primary" args={{ primary: true, label: 'Button' }}>
    {(args) => <Button {...args} />}
  </Story>
</Canvas>

### Secondary Button

<Canvas>
  <Story name="Secondary" args={{ label: 'Button' }}>
    {(args) => <Button {...args} />}
  </Story>
</Canvas>

## Props

<ArgsTable of={Button} />

## Usage Guidelines

- Use primary buttons sparingly, typically one per page
- Secondary buttons can be used multiple times
- Always provide meaningful labels
- Consider accessibility when choosing colors

## Code Example

```jsx
import { Button } from './Button';

function MyComponent() {
  return (
    <div>
      <Button primary onClick={() => alert('Primary clicked!')}>
        Primary Action
      </Button>
      <Button onClick={() => alert('Secondary clicked!')}>
        Secondary Action
      </Button>
    </div>
  );
}
### Documentation Pages
```javascript
// .storybook/main.js
module.exports = {
  stories: [
    '../src/**/*.stories.@(js|jsx|ts|tsx)',
    '../docs/**/*.stories.mdx', // Documentation stories
  ],
};

// docs/Introduction.stories.mdx
import { Meta } from '@storybook/addon-docs';

<Meta title="Introduction" />

# Design System

Welcome to our design system documentation.

## Getting Started

This Storybook contains all the components, patterns, and guidelines for our design system.

### Installation

```bash
npm install @company/design-system

Usage

import { Button, Input, Card } from '@company/design-system';

Principles

  1. Consistency - Maintain visual and functional consistency
  2. Accessibility - Ensure all components are accessible
  3. Performance - Optimize for speed and efficiency
  4. Flexibility - Support customization and theming
    ## Deployment
    
    ### Static Build
    ```bash
    # Build Storybook for deployment
    npm run build-storybook
    
    # Output directory
    ls storybook-static/
    
    # Serve locally
    npx http-server storybook-static
    

Netlify Deployment

# netlify.toml
[build]
  command = "npm run build-storybook"
  publish = "storybook-static"

[build.environment]
  NODE_VERSION = "16"

# Deploy
npm install -g netlify-cli
netlify deploy --prod --dir=storybook-static

Vercel Deployment

// vercel.json
{
  "buildCommand": "npm run build-storybook",
  "outputDirectory": "storybook-static",
  "framework": null
}

GitHub Pages

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

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - name: Setup Node
      uses: actions/setup-node@v3
      with:
        node-version: '16'
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Build Storybook
      run: npm run build-storybook

    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./storybook-static

Docker Deployment

# Dockerfile
FROM node:16-alpine as builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build-storybook

FROM nginx:alpine
COPY --from=builder /app/storybook-static /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

AWS S3 + CloudFront

# Build and deploy to S3
npm run build-storybook
aws s3 sync storybook-static/ s3://my-storybook-bucket --delete
aws cloudfront create-invalidation --distribution-id ABCD1234 --paths "/*"

Advanced Features

Custom Webpack Configuration

// .storybook/main.js
const path = require('path');

module.exports = {
  webpackFinal: async (config) => {
    // Add alias
    config.resolve.alias = {
      ...config.resolve.alias,
      '@': path.resolve(__dirname, '../src'),
      '@components': path.resolve(__dirname, '../src/components'),
    };

    // Add custom loader
    config.module.rules.push({
      test: /\.scss$/,
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'sass-loader',
          options: {
            additionalData: `@import "@/styles/variables.scss";`,
          },
        },
      ],
    });

    // Add plugin
    config.plugins.push(
      new webpack.DefinePlugin({
        __VERSION__: JSON.stringify(process.env.npm_package_version),
      })
    );

    return config;
  },
};

Custom Babel Configuration

// .storybook/main.js
module.exports = {
  babel: async (options) => ({
    ...options,
    plugins: [
      ...options.plugins,
      ['babel-plugin-styled-components', { displayName: true }],
    ],
  }),
};

Environment-Specific Configuration

// .storybook/main.js
const isDevelopment = process.env.NODE_ENV === 'development';

module.exports = {
  addons: [
    '@storybook/addon-essentials',
    ...(isDevelopment ? ['@storybook/addon-a11y'] : []),
  ],

  features: {
    storyStoreV7: !isDevelopment, // Disable in development for faster builds
  },

  webpackFinal: async (config) => {
    if (isDevelopment) {
      // Development-specific webpack config
      config.optimization.minimize = false;
    }

    return config;
  },
};

Custom Manager UI

// .storybook/manager.js
import { addons } from '@storybook/addons';

addons.setConfig({
  // Show/hide panels
  showPanel: true,
  panelPosition: 'bottom', // 'bottom' | 'right'

  // Sidebar
  showNav: true,
  showToolbar: true,

  // Initial active panel
  selectedPanel: 'storybook/controls/panel',

  // Sidebar tree expansion
  initialActive: 'sidebar',
  sidebar: {
    showRoots: false,
    collapsedRoots: ['other'],
  },

  // Toolbar
  toolbar: {
    title: { hidden: false },
    zoom: { hidden: false },
    eject: { hidden: false },
    copy: { hidden: false },
    fullscreen: { hidden: false },
  },
});

Performance

Lazy Loading

// .storybook/main.js
module.exports = {
  features: {
    storyStoreV7: true, // Enable lazy loading
  },

  stories: [
    {
      directory: '../src/components',
      files: '**/*.stories.*',
      titlePrefix: 'Components',
    },
    {
      directory: '../src/pages',
      files: '**/*.stories.*',
      titlePrefix: 'Pages',
    },
  ],
};

Bundle Optimization

// .storybook/main.js
module.exports = {
  webpackFinal: async (config) => {
    // Code splitting
    config.optimization.splitChunks = {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    };

    // Tree shaking
    config.optimization.usedExports = true;
    config.optimization.sideEffects = false;

    return config;
  },
};

Memory Optimization

// .storybook/preview.js
export const parameters = {
  // Reduce memory usage
  options: {
    storySort: {
      method: 'alphabetical',
      order: ['Introduction', 'Components', 'Pages'],
      locales: 'en-US',
    },
  },

  // Disable source code addon for better performance
  docs: {
    source: {
      state: 'closed',
    },
  },
};

// Cleanup decorators
export const decorators = [
  (Story, context) => {
    // Cleanup on story change
    React.useEffect(() => {
      return () => {
        // Cleanup logic
      };
    }, [context.id]);

    return <Story />;
  },
];

Integration

Design Tools Integration

// Figma integration
export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    design: {
      type: 'figma',
      url: 'https://www.figma.com/file/ABC123/Design-System?node-id=123%3A456',
    },
  },
};

// Sketch integration
export const SketchExample = {
  parameters: {
    design: {
      type: 'sketch',
      url: 'https://sketch.cloud/s/abc123',
    },
  },
};

// Adobe XD integration
export const XDExample = {
  parameters: {
    design: {
      type: 'adobe-xd',
      url: 'https://xd.adobe.com/view/abc123',
    },
  },
};

Testing Integration

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  testPathIgnorePatterns: ['/node_modules/', '/storybook-static/'],

  // Test stories
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.(js|jsx|ts|tsx)',
    '<rootDir>/src/**/?(*.)(spec|test).(js|jsx|ts|tsx)',
    '<rootDir>/src/**/*.stories.test.(js|jsx|ts|tsx)',
  ],
};

// Chromatic integration
// .github/workflows/chromatic.yml
name: 'Chromatic'
on: push

jobs:
  chromatic-deployment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - run: yarn
      - uses: chromaui/action@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

CI/CD Integration

# .github/workflows/storybook-tests.yml
name: Storybook Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: 'npm'

      - run: npm ci
      - run: npm run build-storybook --quiet
      - run: npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
          "npx http-server storybook-static --port 6006 --silent" \
          "npx wait-on http://127.0.0.1:6006 && npm run test-storybook"

Troubleshooting

Common Issues

# Port already in use
npm run storybook -- --port 9001

# Clear cache
rm -rf node_modules/.cache/storybook

# Webpack issues
npm run storybook -- --no-manager-cache

# TypeScript issues
npm run storybook -- --no-dll

# Memory issues
NODE_OPTIONS="--max-old-space-size=4096" npm run storybook

Debug Mode

# Enable debug logging
DEBUG=storybook:* npm run storybook

# Webpack debug
npm run storybook -- --debug-webpack

# Verbose output
npm run storybook -- --verbose

Performance Issues

// .storybook/main.js
module.exports = {
  // Disable source maps in development
  webpackFinal: async (config) => {
    if (process.env.NODE_ENV === 'development') {
      config.devtool = false;
    }
    return config;
  },

  // Reduce bundle size
  features: {
    storyStoreV7: true,
    buildStoriesJson: true,
  },
};

Build Issues

# Clear all caches
rm -rf node_modules/.cache
rm -rf storybook-static
npm run build-storybook

# Check for conflicting dependencies
npm ls

# Update Storybook
npx storybook@latest upgrade

Best Practices

Story Organization

  • Hierarchical Structure: Use consistent naming patterns
  • Logical Grouping: Group related components together
  • Clear Naming: Use descriptive story names
  • Documentation: Include comprehensive documentation

Component Development

  • Isolation: Develop components in isolation
  • Props Interface: Design clear, consistent prop interfaces
  • Default Values: Provide sensible defaults
  • Error Handling: Handle edge cases gracefully

Testing Strategy

  • Visual Testing: Use visual regression testing
  • Interaction Testing: Test user interactions
  • Accessibility Testing: Ensure components are accessible
  • Unit Testing: Test component logic

Performance Optimization

  • Lazy Loading: Use story store v7 for lazy loading
  • Bundle Splitting: Optimize webpack configuration
  • Memory Management: Clean up resources properly
  • Build Optimization: Minimize build times

Team Collaboration

  • Design System: Maintain a consistent design system
  • Documentation: Keep documentation up to date
  • Code Reviews: Review stories and components
  • Automation: Automate testing and deployment

Summary

Storybook is an essential tool for modern frontend development that enables:

  • Component Isolation: Develop and test UI components independently
  • Visual Documentation: Create living documentation for design systems
  • Interactive Development: Build components with real-time feedback
  • Testing Integration: Visual, interaction, and accessibility testing
  • Team Collaboration: Share components across teams and stakeholders
  • Design System Management: Maintain consistent UI patterns

Key benefits include faster development cycles, better component quality, improved team collaboration, and comprehensive documentation. Storybook supports all major frontend frameworks and provides extensive customization options through addons and configuration.