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.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- storybook-stories Installs to ~/.agents/skills/storybook-stories/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install storybook-stories The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project storybook-stories Installs to .agents/skills/storybook-stories/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/storybook-stories.zipunzip -o storybook-stories.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) storybook-stories PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
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
- Screenshot diffs of stories across commits: use visual-regression-testing
- Unit tests of component logic without Storybook: use react-testing-library
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.
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
- 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 inpackage.json, and global decorators already registered. - Look at two existing stories and match their file location, naming and title scheme.
- Read the component and its tests; list the states that matter (empty, loading, error, long content, disabled, each variant, RTL, dark theme).
- 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>plusStoryObj<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 kind | Stories |
|---|---|
| Presentational primitive | Each variant, size extremes, disabled, long text, with icon |
| Form control | Empty, filled, invalid with message, disabled, required, keyboard entry play test |
| Data-driven component | Loading, 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 screen | Logged-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’sdecorators. - 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
| Dependency | Approach |
|---|---|
| HTTP calls | MSW 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 time | Mock the date module or pass the date as a prop; never let stories depend on Date.now() |
| Random IDs | Seed 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
playalso run their assertions. The a11y addon reports axe violations per story; withparameters.a11y.test: "error"they fail the run,"todo"reports only. - Use
awaiton everyuserEventcall andfindBy*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.componentfor 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). playruns 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.
argTypeswithcontrol: falsehides a prop from controls but not from the docs table; usetable: { disable: true }for internal props.
Verification
-
npx storybook devloads 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 buildsucceeds; 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.yaml
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.