Storybook Stories skill for Codex

Stories that document and test every state. Codex writes Storybook stories that cover the states a component actually has, such as loading, error, overflow and disabled, rather than one per prop. Stories are typed with satisfies Meta, callbacks use fn(), data comes from MSW or module mocks so nothing hits the network, and play functions plus the a11y addon run as Vitest tests in CI.

  • Frontend
  • $storybook-stories
  • 177 lines
  • 2 files
  • ~1,850 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- storybook-stories

Installs to ~/.agents/skills/storybook-stories/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $storybook-stories, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Writes Storybook stories in CSF 3 with typed Meta and StoryObj, args and argTypes, decorators and providers, play-function interaction tests run through the Vitest addon, accessibility checks with addon-a11y, module mocking, and autodocs pages, for React, Vue, Svelte, Angular or web components on Storybook 9 and 10. Use when a user asks to add stories for a component, cover its states, write interaction tests in Storybook, document a design system, fix stories broken by providers or data fetching, or upgrade Storybook.

Use it when

  • A component needs stories or its existing stories are thin
  • Interaction tests should live next to the component in Storybook
  • Stories break because of providers, routing or network calls
  • A design system needs generated docs pages

Use something else when

How to check the result

Run npx vitest --project=storybook (or the test-runner) with play and a11y checks passing, then npx storybook build, and open new stories with the network disabled.

The mistake it prevents

Letting stories fetch real APIs or read Date.now(), which makes them flaky in CI and useless as visual baselines.

~/.agents/skills/storybook-stories/SKILL.md

--- name: storybook-stories description: Writes Storybook stories in CSF 3 with typed Meta and StoryObj, args and argTypes, decorators and providers, play-function interaction tests run… license: MIT ---

Done well means: each component has one story per meaningful state (not per prop), stories render in isolation without network calls, interactive behavior is asserted in play functions that run in CI, and the docs page tells a consumer how to use the component without reading its source.

Before writing anything

  1. Read AGENTS.md, .storybook/main.* and .storybook/preview.*. Note the framework package (e.g. @storybook/react-vite, @storybook/nextjs-vite, @storybook/vue3-vite), the Storybook version in package.json, and global decorators already registered.
  2. Look at two existing stories and match their file location, naming and title scheme.
  3. Read the component and its tests; list the states that matter (empty, loading, error, long content, disabled, each variant, RTL, dark theme).
  4. Storybook 10 is ESM-only: config and story files must be valid ESM, and Node 20.16+, 22.19+ or 24+ is required. Upgrades: npx storybook@latest upgrade (needs network; ask in a sandbox).

Story file template (React; other renderers are analogous)

// 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,
  title: "Primitives/Button",           // omit to derive from the file path
  tags: ["autodocs"],
  args: { children: "Save", onClick: fn() },
  argTypes: {
    variant: { control: "inline-radio", options: ["primary", "secondary", "danger"] },
    onClick: { table: { category: "Events" } },
  },
  parameters: { layout: "centered" },
} satisfies Meta<typeof Button>;

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

export const Primary: Story = {};
export const Danger: Story = { args: { variant: "danger", children: "Delete project" } };
export const Disabled: Story = { args: { disabled: true } };
export const LongLabel: Story = {
  args: { children: "Save changes and notify every collaborator on this workspace" },
};

export const CallsOnClick: Story = {
  play: async ({ args, canvas, userEvent }) => {
    await userEvent.click(canvas.getByRole("button", { name: /save/i }));
    await expect(args.onClick).toHaveBeenCalledOnce();
  },
};
  • satisfies Meta<typeof Button> plus StoryObj<typeof meta> gives type errors when a story misses a required arg.
  • Put shared args on meta; stories override only what differs.
  • Use fn() for every callback so calls show in the Actions panel and can be asserted.
  • Import test utilities from storybook/test (Storybook 9+). Older projects use @storybook/test; switch when upgrading.

Which stories to write

Component kindStories
Presentational primitiveEach variant, size extremes, disabled, long text, with icon
Form controlEmpty, filled, invalid with message, disabled, required, keyboard entry play test
Data-driven componentLoading, empty, error, typical data, overflow (100+ rows, long strings)
Overlay (dialog, menu)Opened via play function so the open state is visible and tested
Page or screenLogged-out, logged-in, permission denied, with mocked network data

Skip stories that only flip one boolean with no visual or behavioral difference; use controls.

Decorators, providers and context

// .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, { globals }) => (
      <ThemeProvider theme={globals.theme ?? "light"}><Story /></ThemeProvider>
    ),
  ],
  globalTypes: {
    theme: { toolbar: { title: "Theme", items: ["light", "dark"], dynamicTitle: true } },
  },
  parameters: { a11y: { test: "error" } },
};
export default preview;
  • Global providers (theme, i18n, query client, router) go in preview; story-specific wrappers go on the story’s decorators.
  • Create a fresh query client per story to avoid cache leaks between stories.
  • Router-dependent components: use the framework’s router parameters (Next.js framework packages expose parameters.nextjs) or a memory router decorator.

Data and module mocking

DependencyApproach
HTTP callsMSW with msw-storybook-addon; handlers in parameters.msw.handlers
Imported modules (session, analytics, date)Storybook module mocking: register with sb.mock() in preview (Storybook 9.1+/10), then set return values in beforeEach
Current date or timeMock the date module or pass the date as a prop; never let stories depend on Date.now()
Random IDsSeed or stub them; snapshots and visual tests flake otherwise
// .storybook/preview.ts:  import { sb } from "storybook/test";  sb.mock(import("../src/lib/session.ts"));
// in the story file:
import { mocked } from "storybook/test";
import { getSession } from "../lib/session";

export const Expired: Story = {
  beforeEach: async () => {
    mocked(getSession).mockResolvedValue({ user: null });
  },
};

Interaction and a11y tests in CI

npx storybook add @storybook/addon-vitest     # sets up Vitest browser mode with Playwright
npx vitest --project=storybook                # run all stories as tests
  • Every story is a smoke test (it must render without throwing); stories with play also run their assertions. The a11y addon reports axe violations per story; with parameters.a11y.test: "error" they fail the run, "todo" reports only.
  • Use await on every userEvent call and findBy* for async UI.
  • Query by role and accessible name, as in Testing Library; never by class names.
  • Projects not on Vite can still use @storybook/test-runner (Jest + Playwright against a built or running Storybook).
  • Visual regression of stories belongs to $visual-regression-testing.

Docs

  • tags: ["autodocs"] on meta generates a docs page from args, argTypes and JSDoc comments on the component props. Write the prop JSDoc; it becomes the table description.
  • Add a short parameters.docs.description.component for usage guidance (“Use for the one primary action on a screen”).
  • Use MDX only for guides that are more than a component page; keep it next to the stories.

Gotchas

  • Stories that import the app’s root (App.tsx) pull in the whole app and its side effects. Import the component directly.
  • CSS not appearing: global styles must be imported in preview, and Tailwind needs the stories path in its content sources (v3) or @source (v4).
  • play runs after render on every visit, including in docs mode; keep it idempotent and fast.
  • Story names come from export names; renaming an export breaks links and Chromatic baselines.
  • argTypes with control: false hides a prop from controls but not from the docs table; use table: { disable: true } for internal props.

Verification

  • npx storybook dev loads with no console errors on the new stories.
  • npx vitest --project=storybook (or the test-runner) passes, including play and a11y.
  • Each story renders offline (network disabled) and in both themes.
  • npx storybook build succeeds; this is what CI and hosting use.

Output

Report the stories added per component, the states covered, play tests and what they assert, mocks introduced, and the command that runs them in CI.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Storybook Stories"
  short_description: "Stories that document and test every state"
  default_prompt: "$storybook-stories Write Storybook stories for the DataTable component covering loading, empty and error"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc