Claude Code for Storybook: Component Documentation and Visual Testing — Claude Skills 360 Blog
Blog / Development / Claude Code for Storybook: Component Documentation and Visual Testing
Development

Claude Code for Storybook: Component Documentation and Visual Testing

Published: June 29, 2026
Read time: 8 min read
By: Claude Skills 360

Storybook is the industry standard for component development in isolation — but most teams use it as read-only documentation rather than an active testing tool. Claude Code generates stories that cover real usage patterns, interaction tests that replace some Playwright tests, and Chromatic integration that catches visual regressions before they reach production.

This guide covers Storybook with Claude Code: writing stories, interaction tests, visual regression, and component documentation.

Storybook Setup

CLAUDE.md for Storybook Projects

## Storybook Component Library

- Storybook 8.x with React 18 + TypeScript + Vite
- Testing: @storybook/test (vitest-based)
- Visual regression: Chromatic (linked to GitHub)
- Accessibility: @storybook/addon-a11y (axe-core integration)

## Story conventions
- One story file per component: ComponentName.stories.tsx
- Required story per component: Default, with all required props
- Additional stories: key variants and states (Loading, Error, Empty, etc.)
- Use args for interactive controls — not hardcoded JSX
- Interaction tests: test() blocks in stories, not separate test files

## What NOT to put in stories
- Complex business logic stories — demonstrate UI states only
- Stories that require network calls — use MSW for API mocking
- Stories with real Supabase/Stripe/etc. — use mock service worker

## Story naming
- Default: renders with minimal required props
- Variant names: WithIcon, Disabled, Loading, Error, Empty, Selected
- Don't name stories after scenarios ("UserClicksButton") — name by state

Component Story Structure

Write comprehensive stories for a DataTable component.
Cover: default, loading, empty, error, with selection, with sorting.
// src/components/DataTable/DataTable.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { DataTable } from './DataTable';

// Column definitions for stories
const columns = [
  { key: 'name', header: 'Name', sortable: true },
  { key: 'email', header: 'Email', sortable: true },
  { key: 'role', header: 'Role' },
  { key: 'status', header: 'Status', cell: (row) => <StatusBadge status={row.status} /> },
];

const users = Array.from({ length: 20 }, (_, i) => ({
  id: `user-${i}`,
  name: `User ${i + 1}`,
  email: `user${i + 1}@example.com`,
  role: i % 3 === 0 ? 'Admin' : 'Member',
  status: i % 4 === 0 ? 'inactive' : 'active',
}));

const meta: Meta<typeof DataTable> = {
  title: 'Components/DataTable',
  component: DataTable,
  tags: ['autodocs'],
  args: {
    // Default args — all stories inherit these
    columns,
    onSort: fn(),
    onRowSelect: fn(),
    onPageChange: fn(),
  },
};

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

// Default — most common usage
export const Default: Story = {
  args: {
    data: users,
    totalRows: users.length,
  },
};

// Loading skeleton
export const Loading: Story = {
  args: {
    data: [],
    loading: true,
  },
};

// Empty state
export const Empty: Story = {
  args: {
    data: [],
    loading: false,
    emptyMessage: 'No users found. Invite your team to get started.',
  },
};

// With row selection
export const WithSelection: Story = {
  args: {
    data: users,
    selectable: true,
    selectedIds: ['user-0', 'user-3'],
    totalRows: users.length,
  },
};

// Sorted column
export const SortedByName: Story = {
  args: {
    data: [...users].sort((a, b) => a.name.localeCompare(b.name)),
    totalRows: users.length,
    sortColumn: 'name',
    sortDirection: 'asc',
  },
};

// Paginated — page 2
export const Paginated: Story = {
  args: {
    data: users.slice(10, 20),
    totalRows: 87,
    currentPage: 2,
    pageSize: 10,
  },
};

Interaction Tests

Add interaction tests to the DataTable stories.
Test that sorting works when you click a column header.
import { expect, userEvent, within } from '@storybook/test';

export const SortInteraction: Story = {
  args: {
    data: users,
    totalRows: users.length,
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    
    // Click the Name column header to sort
    const nameHeader = canvas.getByRole('columnheader', { name: /name/i });
    await userEvent.click(nameHeader);
    
    // Verify the onSort callback was called with correct args
    expect(args.onSort).toHaveBeenCalledWith({ column: 'name', direction: 'asc' });
    
    // Click again to reverse sort direction
    await userEvent.click(nameHeader);
    expect(args.onSort).toHaveBeenCalledWith({ column: 'name', direction: 'desc' });
  },
};

export const SelectAllInteraction: Story = {
  args: {
    data: users.slice(0, 5),
    totalRows: 5,
    selectable: true,
    selectedIds: [],
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    
    // Click "select all" checkbox
    const selectAll = canvas.getByRole('checkbox', { name: /select all/i });
    await userEvent.click(selectAll);
    
    // All rows should be selected
    expect(args.onRowSelect).toHaveBeenCalledWith(
      expect.arrayContaining(users.slice(0, 5).map(u => u.id))
    );
    
    // Check individual checkboxes are marked
    const checkboxes = canvas.getAllByRole('checkbox');
    checkboxes.forEach(checkbox => expect(checkbox).toBeChecked());
  },
};

Run interaction tests in CI:

# Runs in headless mode — no browser window
npx storybook test --ci

MSW for API Mocking

The UserProfile component fetches data from our API.
Set up MSW so it works in Storybook without hitting the real API.
// .storybook/preview.ts
import { initialize, mswLoader } from 'msw-storybook-addon';

initialize();

export const loaders = [mswLoader];
// src/components/UserProfile/UserProfile.stories.tsx
import { http, HttpResponse } from 'msw';

export const Default: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/users/:id', ({ params }) => {
          return HttpResponse.json({
            id: params.id,
            name: 'Alice Johnson',
            email: '[email protected]',
            bio: 'Senior frontend engineer. Coffee enthusiast.',
            avatar: 'https://i.pravatar.cc/150?img=1',
          });
        }),
      ],
    },
  },
};

export const NotFound: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/users/:id', () => {
          return HttpResponse.json({ error: 'User not found' }, { status: 404 });
        }),
      ],
    },
  },
};

export const Loading: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/users/:id', async () => {
          await new Promise(resolve => setTimeout(resolve, 10000)); // Never resolves
          return HttpResponse.json({});
        }),
      ],
    },
  },
};

Visual Regression with Chromatic

Set up Chromatic to catch visual regressions on every PR.
# .github/workflows/chromatic.yml
name: Visual Regression

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  chromatic:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Chromatic needs git history for base comparisons
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - run: npm ci
      
      - name: Publish to Chromatic
        uses: chromaui/action@latest
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
          exitZeroOnChanges: true  # PR comments on changes without failing
          autoAcceptChanges: main  # Auto-accept on main (no review needed)

On every PR, Chromatic renders each story in a headless browser, diffs against the previous accepted baseline, and posts a review link to the PR. Visual changes require explicit acceptance — which happens in the Chromatic UI, not just by merging the PR.

For testing components beyond Storybook — integration tests, E2E tests — see the testing strategies guide for where each tool fits. For React component patterns that make components easier to document in Storybook, the code review guide covers component design principles. The Claude Skills 360 bundle includes Storybook skill sets for component documentation and visual testing. Start with the free tier to generate stories for your component library.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free