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.
- name
storybook-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.
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
| Version | What matters |
|---|---|
| 10.x | ESM-only (Node 20.16+, 22.19+ or 24+); sb.mock module mocking; CSF Factories available; @storybook/addon-vitest for tests |
| 9.x | Core 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; useconst canvas = within(canvasElement)and importuserEventfrom@storybook/test. - CSF Factories (
definePreviewin 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
parametersandglobalsin the story, not by forking decorators.
Data and module mocking
| Dependency | Mock 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) |
| Time | Fix Date in a decorator or beforeEach; never render new Date() unmocked in a visual test |
| Container components that fetch | Prefer 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-runneragainst 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.
awaiteveryuserEventcall and everyexpect(for async matchers).- Use
findBy*/waitForfor async UI; no fixed sleeps. - Use
step("label", async () => ...)to group long flows so failures point at a step.
Visual review
| Option | When |
|---|---|
| Chromatic | Team review UI, per-PR baselines, TurboSnap; paid beyond free tier |
| Playwright screenshots over built Storybook | Self-hosted, deterministic Docker environment |
| Vitest browser mode screenshots | Lightweight; 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
argspassed 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
argTypesoverrides drift from component props; let docgen infer them and only override control types. - Building Storybook in CI without
--testflag is fine; with Storybook 8--testproduces a faster, docs-free build for test runners. - Static build assets: put fixtures in
staticDirsinstead of importing huge JSON into stories.
Verification
npx storybook dev -p 6006— the new stories render with no console errors.npx vitest --project=storybook(ortest-storybook) passes, including play functions and a11y checks.npx storybook buildsucceeds (catches import-time errors dev mode hides).- 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.