← All articles
FRONTEND Storybook: Build UI Components in Isolation 2026-03-04 · 6 min read · storybook · components · ui

Storybook: Build UI Components in Isolation

Frontend 2026-03-04 · 6 min read storybook components ui react vue frontend testing documentation

Storybook: Build UI Components in Isolation

Component-driven development is the dominant paradigm for modern UIs — but developing components inside the full application context is slow. You need the right mock data, navigate to the right screen, and toggle the right state just to see a button in its disabled state. Storybook solves this by giving each component its own development environment.

Storybook is an open-source tool for building UI components in isolation. Every component gets a "story" — a defined state or variant. Storybook renders them in a dedicated sandbox, completely decoupled from your application's routing, data fetching, and business logic.

What Storybook Does

Installation

New Project

# In your existing project root
npx storybook@latest init

Storybook auto-detects your framework (React, Vue, etc.) and installs the right configuration. After setup, run:

npm run storybook

Storybook opens at http://localhost:6006.

With Vite

For projects using Vite:

npx storybook@latest init --builder vite

Or if Storybook already chose Webpack and you want to switch:

npm install --save-dev @storybook/builder-vite

Update .storybook/main.ts:

const config: StorybookConfig = {
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
};

Writing Your First Story

Stories are files co-located with your components. The convention is ComponentName.stories.tsx.

Given a Button component:

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

export const Button = ({ label, variant = 'primary', disabled, onClick }: ButtonProps) => (
  <button
    className={`btn btn-${variant}`}
    disabled={disabled}
    onClick={onClick}
  >
    {label}
  </button>
);

Write the story file:

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

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],   // Enable auto-generated docs
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'danger'],
    },
  },
};

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

// Stories are named exports
export const Primary: Story = {
  args: {
    label: 'Click me',
    variant: 'primary',
  },
};

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

export const Danger: Story = {
  args: {
    label: 'Delete',
    variant: 'danger',
  },
};

export const Disabled: Story = {
  args: {
    label: 'Unavailable',
    disabled: true,
  },
};

Each named export is a separate story. In the Storybook UI, you'll see a tree:

Components
  └── Button
        ├── Primary
        ├── Secondary
        ├── Danger
        └── Disabled

Controls Panel

With argTypes configured, Storybook generates a Controls panel at the bottom of the canvas. You can:

Changes update the component in real time — no code editing required. This is great for QA, design review, and exploring edge cases.

Auto-Generated Documentation

Add tags: ['autodocs'] to your meta object (shown above). Storybook generates a documentation page for the component with:

To add descriptions, use JSDoc on your props:

interface ButtonProps {
  /** The button's text label */
  label: string;
  /** Visual style variant */
  variant?: 'primary' | 'secondary' | 'danger';
  /** Prevents user interaction */
  disabled?: boolean;
}

The docs page is automatically updated when you change types — no manual documentation maintenance.

Accessibility Testing

Install the a11y addon:

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

Add to .storybook/main.ts:

const config: StorybookConfig = {
  addons: [
    '@storybook/addon-a11y',
    // ... other addons
  ],
};

Each story now has an Accessibility panel showing WCAG violations from axe-core. Issues are categorized by severity (critical, serious, moderate, minor).

You can configure per-story accessibility rules:

export const WithFocus: Story = {
  parameters: {
    a11y: {
      // Disable specific rules for this story
      config: {
        rules: [{ id: 'color-contrast', enabled: false }],
      },
    },
  },
};

Mocking Data and Imports

Complex components need mock data. Use Storybook's decorator pattern:

// ProductCard.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { ProductCard } from './ProductCard';

const meta: Meta<typeof ProductCard> = {
  title: 'Commerce/ProductCard',
  component: ProductCard,
  decorators: [
    (Story) => (
      <div style={{ maxWidth: '320px', margin: '0 auto' }}>
        <Story />
      </div>
    ),
  ],
};

export const WithProduct: Story = {
  args: {
    product: {
      id: '1',
      name: 'Wireless Headphones',
      price: 79.99,
      image: '/placeholder.jpg',
      inStock: true,
    },
  },
};

export const OutOfStock: Story = {
  args: {
    product: {
      ...WithProduct.args.product,
      inStock: false,
    },
  },
};

Mocking API Calls

Use MSW (Mock Service Worker) for component stories that make API calls:

npm install --save-dev msw msw-storybook-addon

In .storybook/preview.tsx:

import { initialize, mswLoader } from 'msw-storybook-addon';

initialize();

export const loaders = [mswLoader];

In your story:

import { http, HttpResponse } from 'msw';

export const WithLoadedData: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/products', () => {
          return HttpResponse.json([
            { id: '1', name: 'Product A', price: 29.99 },
            { id: '2', name: 'Product B', price: 49.99 },
          ]);
        }),
      ],
    },
  },
};

export const WithError: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/products', () => {
          return new HttpResponse(null, { status: 500 });
        }),
      ],
    },
  },
};

Visual Regression Testing with Chromatic

Chromatic is Storybook's companion service for visual regression testing. On every commit, it captures screenshots of all stories and highlights pixel differences.

npm install --save-dev chromatic
# Run locally
npx chromatic --project-token=YOUR_TOKEN

# Or in CI (GitHub Actions)
- name: Publish to Chromatic
  uses: chromaui/action@latest
  with:
    projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

Chromatic shows a diff UI where reviewers approve or reject visual changes. This prevents accidental UI regressions from sneaking into production.

Interaction Testing

Test component behavior with simulated user events:

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

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

    const emailInput = canvas.getByLabelText('Email');
    await userEvent.type(emailInput, '[email protected]');

    const submitButton = canvas.getByRole('button', { name: /submit/i });
    await userEvent.click(submitButton);

    await expect(canvas.getByText('Success!')).toBeInTheDocument();
  },
};

These play functions run automatically in the Storybook canvas and can be included in CI via:

npx storybook test

Storybook Configuration

.storybook/main.ts controls Storybook's setup:

import type { StorybookConfig } from '@storybook/react-vite';

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

.storybook/preview.tsx sets global decorators and parameters:

import type { Preview } from '@storybook/react';
import '../src/styles/globals.css';   // Import global styles

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    layout: 'centered',
  },
  decorators: [
    // Wrap all stories with providers
    (Story) => (
      <ThemeProvider theme="light">
        <Story />
      </ThemeProvider>
    ),
  ],
};

Deploying Storybook

Build a static version for sharing:

npm run build-storybook

This outputs to storybook-static/. Deploy it anywhere static files are served:

# GitHub Pages
npx gh-pages -d storybook-static

# Netlify
# Drag and drop storybook-static/ to netlify.com

# Vercel
vercel storybook-static/

Teams often deploy Storybook to a subdomain (e.g., storybook.yourdomain.com) so designers and PMs can review component states without running the dev environment locally.

Storybook vs Alternatives

Tool Focus Best For
Storybook Component isolation + docs Large component libraries
Histoire Vue/Svelte-first Vue-heavy projects
Ladle Minimal Storybook alt Simple React projects
Sandpack In-browser sandbox Educational/docs sites

Storybook dominates for React projects at scale. Histoire is the go-to for Vue projects. Ladle is worth considering if Storybook feels heavy for a small project.

Wrapping Up

Storybook shifts the unit of UI development from pages to components. Once set up, you can build and review every component state without spinning up your full application, which dramatically speeds up UI development and review cycles.

The initial setup investment — stories, mocks, addons — pays off quickly for component libraries or design systems. For application development, even a few stories for your most complex components can save hours of debugging.


Frontend tooling guides like this land in the DevTools Guide newsletter every week — subscribe to stay sharp.