Storybook Components skill for Claude Code

Stories as tested fixtures, not a demo gallery. Claude writes one story per meaningful component state, types meta with satisfies so bad args fail to compile, mocks every network and module dependency so stories render offline, and turns play functions into CI tests through the Vitest addon with accessibility checks. It checks the Storybook major first, because imports and test tooling changed between 8, 9 and 10.

  • namestorybook-components
  • size188 lines · ≈2,150 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Builds and maintains Storybook 8, 9 and 10 workspaces for component libraries — CSF3 stories with typed meta and args, decorators and providers, mocking data and modules, play-function interaction tests run through the Vitest addon, accessibility checks, autodocs, and a visual review workflow with Chromatic or screenshot tests in CI. Use when adding stories for a component, setting up or upgrading Storybook, stories break because of providers or network calls, or when turning stories into automated component tests.

Try asking

  • Write stories for this DataTable covering empty, loading, error and huge datasets
  • Set up Storybook interaction tests in CI for our Vite React library
  • Upgrade our Storybook 8 setup to 10 and fix the broken imports

Use it when

  • Adding or reviewing stories for UI components
  • Setting up or upgrading Storybook and its addons
  • Stories break because of providers, routing or network calls
  • Turning stories into automated interaction, a11y or visual tests

Skip it when

  • You need pixel-diff infrastructure for full pages — use visual-regression
  • You are testing a component in isolation with plain unit tests — use testing-library

How to check the result

Storybook dev renders the new stories without console errors, the story test project passes including play functions and a11y, and the static build succeeds.

The common mistake it prevents

Letting stories call real APIs or render the current time, which makes visual diffs and interaction tests flaky.

~/.claude/skills/storybook-components/SKILL.md

--- name: storybook-components description: Builds and maintains Storybook 8, 9 and 10 workspaces for component libraries — CSF3 stories with typed meta and args, decorators and… license: MIT ---

A healthy Storybook has one story per meaningful state (not per prop), stories that render without a backend, interactions asserted with play functions that run in CI, and every new component reviewed visually before merge. Stories are test fixtures first and documentation second.

Step 0 — version and builder

npm ls storybook @storybook/react-vite @storybook/nextjs @storybook/nextjs-vite @storybook/addon-vitest
ls .storybook && cat .storybook/main.* .storybook/preview.*
node -v
VersionWhat matters
10.xESM-only (Node 20.16+, 22.19+ or 24+); sb.mock module mocking; CSF Factories available; @storybook/addon-vitest for tests
9.xCore packages consolidated into storybook (storybook/test, storybook/actions); Vitest addon replaces the test runner for Vite projects; essentials built in
8.x@storybook/test for fn, expect, userEvent; @storybook/addon-essentials; @storybook/test-runner (Playwright) for CI

Upgrade with npx storybook@latest upgrade (runs automigrations), one major at a time, then npx storybook doctor. Import test utilities from the path matching the version: storybook/test (9+) or @storybook/test (8).

New setup: npm create storybook@latest — prefer a Vite-based framework package (@storybook/react-vite, @storybook/vue3-vite, @storybook/sveltekit, @storybook/nextjs-vite) because the Vitest addon requires Vite.

Story file template (CSF3)

// src/components/Button/Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, fn } from "storybook/test";
import { Button } from "./Button";

const meta = {
  component: Button,
  args: { children: "Save changes", onClick: fn() },   // fn() spies show in Actions and are assertable
  argTypes: { variant: { control: "inline-radio" } },
  tags: ["autodocs"],
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {};
export const Destructive: Story = { args: { variant: "destructive", children: "Delete project" } };
export const Loading: Story = { args: { loading: true } };
export const LongLabel: Story = { args: { children: "Save changes and notify all 14 collaborators" } };

export const ClickFires: Story = {
  play: async ({ args, canvas, userEvent }) => {
    await userEvent.click(canvas.getByRole("button", { name: /save/i }));
    await expect(args.onClick).toHaveBeenCalledOnce();
  },
};
  • satisfies Meta<typeof X> + StoryObj<typeof meta> gives arg type checking including required props supplied in meta.
  • Omit title; let the file path generate it, unless the project has a naming scheme.
  • In Storybook 8 the play context has canvasElement; use const canvas = within(canvasElement) and import userEvent from @storybook/test.
  • CSF Factories (definePreview in preview, preview.meta(...), meta.story(...)) are an opt-in, more type-safe format in 9/10. Follow whatever the codebase already uses; do not mix formats within one file.

Which stories to write

Write stories for states, not for prop permutations:

  • Default / most common usage
  • Each visual variant the design system defines
  • Empty, loading, error, disabled states
  • Edge content: long text, missing image, 0 and 10,000 items, RTL if supported
  • Interactive flows worth asserting (open/close, validation, keyboard)
  • Composition: the component inside its real parent (card in a list, field in a form)

Skip stories that only flip a boolean with no visual effect; controls cover that.

Decorators and providers

// .storybook/preview.tsx
import type { Preview } from "@storybook/react-vite";
import { ThemeProvider } from "../src/theme";
import "../src/styles/globals.css";

const preview: Preview = {
  decorators: [(Story, ctx) => (
    <ThemeProvider theme={ctx.globals.theme ?? "light"}><Story /></ThemeProvider>
  )],
  globalTypes: { theme: { toolbar: { items: ["light", "dark"], dynamicTitle: true } } },
  parameters: { layout: "centered", a11y: { test: "error" } },
};
export default preview;
  • Load the app’s global CSS and fonts in preview, or every story looks subtly wrong.
  • Router, i18n, query client, store: global decorators with fresh instances per story (a shared QueryClient leaks cached data between stories).
  • Per-story overrides via parameters and globals in the story, not by forking decorators.

Data and module mocking

DependencyMock with
HTTP calls (fetch, axios, GraphQL)MSW via msw-storybook-addon; handlers in parameters.msw.handlers per story
Your own modules (analytics, session, date utils)Storybook 10: sb.mock(import("../src/lib/session")) in preview, then set return values per story in beforeEach (check the docs for the exact mocked()/spy API of your version). 8/9: subpath imports (#lib/session with a storybook condition in package.json imports)
Framework APIs (next/navigation, next/image)Framework package built-ins (@storybook/nextjs* mocks router/navigation; configure with parameters.nextjs)
TimeFix Date in a decorator or beforeEach; never render new Date() unmocked in a visual test
Container components that fetchPrefer splitting into a presentational component with stories + a thin container

Never let a story hit a real API: it makes stories slow, flaky, and visual diffs noisy.

Interaction and accessibility tests in CI

Vite projects (Storybook 9+):

npx storybook add @storybook/addon-vitest   # wires a Vitest project that runs every story
npx vitest --project=storybook              # each story = a test; play functions = assertions
  • Every story is a smoke test (renders without throwing); play functions add behavior assertions; with a11y: { test: "error" } axe violations fail the test.
  • Exclude stories from tests with tags: ["!test"], from the sidebar with ["!dev"].
  • Non-Vite or Storybook 8: @storybook/test-runner against a built Storybook: npx storybook build && npx concurrently -k -s first "npx http-server storybook-static -p 6006 -s" "npx wait-on tcp:6006 && npx test-storybook".

Play function rules:

  • Query by role and accessible name, like Testing Library; never by class.
  • await every userEvent call and every expect (for async matchers).
  • Use findBy* / waitFor for async UI; no fixed sleeps.
  • Use step("label", async () => ...) to group long flows so failures point at a step.

Visual review

OptionWhen
ChromaticTeam review UI, per-PR baselines, TurboSnap; paid beyond free tier
Playwright screenshots over built StorybookSelf-hosted, deterministic Docker environment
Vitest browser mode screenshotsLightweight; same runner as the addon

Make visual tests deterministic: disable animations (prefers-reduced-motion or a chromatic: { pauseAnimationAtEnd: true } parameter), fix fonts loaded locally, mock time and random, set a fixed viewport. Accepting a baseline is a review decision, not a CI retry.

Gotchas

  • args passed to a component that ignores unknown props still show controls; type the meta so missing or misspelled args are compile errors.
  • Global CSS imported in preview but not in the app (or the reverse) creates “works in Storybook, broken in app” bugs; import the same entry file.
  • Stories that depend on story order or shared module state fail when run in parallel.
  • Large argTypes overrides drift from component props; let docgen infer them and only override control types.
  • Building Storybook in CI without --test flag is fine; with Storybook 8 --test produces a faster, docs-free build for test runners.
  • Static build assets: put fixtures in staticDirs instead of importing huge JSON into stories.

Verification

  1. npx storybook dev -p 6006 — the new stories render with no console errors.
  2. npx vitest --project=storybook (or test-storybook) passes, including play functions and a11y checks.
  3. npx storybook build succeeds (catches import-time errors dev mode hides).
  4. Visual diff reviewed for every changed story; unexpected diffs investigated.

Report format

**Storybook:** 10.x, @storybook/react-vite, addon-vitest + a11y (test: error)
**Stories added:** Button (6 states, 1 play), DataTable (empty/loading/error/10k rows)
**Mocks:** MSW handlers for /api/projects; session module mocked
**CI:** vitest --project=storybook in the test job; Chromatic on PRs
**Verified:** dev clean, 42 story tests green, build ok, visual diffs approved

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc