Skip to content

Storybook 8 Reference: Stories, Controls, Interactions, MSW Mocking & Visual Testing

Storybook 8 is a frontend workshop for developing, documenting, and testing UI components in isolation. Each story is a named render of a component with specific props — you can develop components without spinning up the whole app, test every visual state, and auto-generate documentation. Storybook 8 added first-class Vitest integration, so stories double as component tests. The key concept: stories are the source of truth for component states; everything else (docs, visual tests, interaction tests) derives from them.

1. Setup & First Story

Install, main.ts config, stories glob, and writing your first CSF3 story
# npx storybook@latest init   # auto-detects Vite/Next.js/webpack + framework

# .storybook/main.ts — Storybook config:
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
  stories: ["../src/**/*.stories.@(ts|tsx|js|jsx)"],
  addons: [
    "@storybook/addon-essentials",    // docs, controls, actions, viewport
    "@storybook/addon-a11y",          // accessibility checks
    "@storybook/addon-interactions",  // user interaction testing
    "@chromatic-com/storybook",       // visual regression (optional)
  ],
  framework: {
    name: "@storybook/react-vite",    // or react-webpack5, nextjs, etc.
    options: {},
  },
  docs: { autodocs: "tag" },   // auto-generate docs for tagged stories
};
export default config;

// src/components/Button/Button.stories.tsx — Component Story Format 3 (CSF3):
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";

// Meta: component-level config
const meta: Meta<typeof Button> = {
  component: Button,
  title: "UI/Button",          // sidebar path
  tags: ["autodocs"],          // auto-generate docs page
  parameters: {
    layout: "centered",        // centered, fullscreen, padded
  },
  argTypes: {
    variant: { control: "select", options: ["primary", "secondary", "danger"] },
    size:    { control: "radio",  options: ["sm", "md", "lg"] },
    onClick: { action: "clicked" },   // logs to Actions panel
  },
};
export default meta;

type Story = StoryObj<typeof Button>;

// Each named export = one story:
export const Primary: Story = {
  args: { label: "Click me", variant: "primary", size: "md" },
};

export const Disabled: Story = {
  args: { label: "Disabled", variant: "primary", disabled: true },
};

export const AllVariants: Story = {
  render: () => (
    <div style={{ display: "flex", gap: "1rem" }}>
      <Button label="Primary" variant="primary" />
      <Button label="Secondary" variant="secondary" />
      <Button label="Danger" variant="danger" />
    </div>
  ),
};

2. Args, Controls & Decorators

Dynamic args, argTypes controls, global decorators for providers, and parameters
// .storybook/preview.tsx — global config (applies to all stories):
import type { Preview } from "@storybook/react";
import { ThemeProvider } from "../src/providers/ThemeProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import "../src/styles/globals.css";

const preview: Preview = {
  decorators: [
    // Wrap every story with providers:
    (Story) => (
      <QueryClientProvider client={new QueryClient()}>
        <ThemeProvider>
          <Story />
        </ThemeProvider>
      </QueryClientProvider>
    ),
  ],
  parameters: {
    actions: { argTypesRegex: "^on[A-Z].*" },   // auto-instrument all onXxx props
    controls: {
      matchers: {
        color: /(background|color)$/i,   // show color picker for props ending in "color"
        date:  /Date$/i,
      },
    },
    backgrounds: {
      default: "light",
      values: [
        { name: "light", value: "#ffffff" },
        { name: "dark",  value: "#1a1a1a" },
      ],
    },
  },
};
export default preview;

// Story-level decorator — override for one story:
export const DarkTheme: Story = {
  args: { label: "Dark button" },
  decorators: [
    (Story) => (
      <div style={{ background: "#1a1a1a", padding: "2rem" }}>
        <Story />
      </div>
    ),
  ],
};

3. Interaction Testing with play()

Write interaction tests using play() — runs in browser and in Vitest CI
import { within, userEvent, expect } from "@storybook/test";

export const LoginFlow: Story = {
  render: () => <LoginForm onSubmit={fn()} />,
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Find elements (same as Testing Library):
    const emailInput    = canvas.getByLabelText("Email");
    const passwordInput = canvas.getByLabelText("Password");
    const submitButton  = canvas.getByRole("button", { name: "Sign in" });

    // Interact:
    await userEvent.type(emailInput, "alice@example.com");
    await userEvent.type(passwordInput, "secret123");
    await userEvent.click(submitButton);

    // Assert:
    await expect(canvas.getByText("Welcome, Alice!")).toBeInTheDocument();
    await expect(submitButton).toBeDisabled();
  },
};

// Loading state story:
export const SubmittingState: Story = {
  args: { isSubmitting: true, label: "Sign in" },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole("button");
    await expect(button).toBeDisabled();
    await expect(canvas.getByRole("progressbar")).toBeInTheDocument();
  },
};

// Run interaction tests in CI (Storybook 8 + Vitest):
// npx storybook test --watch   // development mode
// npx storybook test           // CI mode
// Or via vitest.config.ts with @storybook/experimental-addon-test

4. MSW Integration & Async Stories

Mock API calls with msw-storybook-addon, loaders for async data, and loading states
// npm install -D msw msw-storybook-addon
// npx msw init public/

// .storybook/preview.tsx:
import { initialize, mswLoader } from "msw-storybook-addon";
initialize({ onUnhandledRequest: "bypass" });

// Add MSW loader globally:
const preview: Preview = {
  loaders: [mswLoader],
  // ...
};

// In stories — mock API per-story:
import { http, HttpResponse } from "msw";

export const WithData: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get("/api/users", () =>
          HttpResponse.json([
            { id: "1", name: "Alice", email: "alice@example.com" },
            { id: "2", name: "Bob",   email: "bob@example.com" },
          ])
        ),
      ],
    },
  },
};

export const LoadingState: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get("/api/users", async () => {
          await new Promise(resolve => setTimeout(resolve, 99999));
          return HttpResponse.json([]);
        }),
      ],
    },
  },
};

export const ErrorState: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get("/api/users", () => HttpResponse.json({ error: "Server error" }, { status: 500 })),
      ],
    },
  },
};

5. Docs, Accessibility & Visual Regression

Auto-generated docs with JSDoc, a11y checks, and Chromatic visual regression
// Auto-generated documentation with JSDoc + Storybook Docs:
/**
 * Primary button for actions. Use `variant="danger"` for destructive actions.
 * Supports all standard HTML button attributes.
 */
export const Button = ({
  label,
  variant = "primary",
  size = "md",
  /** Called when the button is clicked (not when disabled) */
  onClick,
  disabled = false,
}: ButtonProps) => <button ...>{label}</button>;

// The JSDoc comment appears automatically in the Args table.
// Add tags: ["autodocs"] to meta to generate a /docs route for the component.

// MDX docs page — custom documentation:
// Button.mdx:
// import { Canvas, Meta, Story, ArgTable } from "@storybook/blocks";
// <Meta of={ButtonStories} />
// # Button
// <Canvas of={ButtonStories.Primary} />
// <ArgTable of={ButtonStories} />

// Accessibility checks (addon-a11y):
// Stories automatically run axe-core accessibility tests in the "Accessibility" panel.
// Configure per-story:
export const AccessibleForm: Story = {
  parameters: {
    a11y: {
      config: {
        rules: [{ id: "color-contrast", enabled: true }],
      },
    },
  },
};

// Visual regression with Chromatic:
// npx chromatic --project-token=YOUR_TOKEN
// CI: Chromatic compares screenshots per-story, shows diffs in PR.
// Free tier: 5,000 snapshots/month.

// Useful CLI commands:
// npx storybook dev -p 6006      — start dev server
// npx storybook build            — build static site
// npx storybook test             — run interaction tests
// npx storybook dev --docs       — docs mode only

Track Node.js and frontend tooling releases at ReleaseRun. Related: Vitest Reference | React Reference | TypeScript Reference | React EOL Tracker

🔍 Free tool: npm Package Health Checker — check Storybook and its addon packages for known CVEs and active maintenance.

Founded

2023 in London, UK

Contact

hello@releaserun.com