Home › Storybook › Storybook 10 stories and tests Cursor rules
Storybook 10 stories and tests Cursor rules
Cursor rules for Storybook 10: CSF 3 stories per component state, args and fn() actions, play-function interaction tests, a11y checks, module mocks, and the Vitest addon.
CursorStorybook
How to use
Save this as .cursor/rules/storybook.mdc in your repository root, commit it, and your agent will pick it up automatically. Edit the commands to match your project.
Note: Review and test before use. Provided as is, without warranty. See Terms.
.cursor/rules/storybook.mdcDownload ---
description: Storybook 10 story and interaction-test conventions
globs: ["**/*.stories.{ts,tsx,js,jsx,svelte}", "**/*.mdx", ".storybook/**"]
alwaysApply: false
---
# Storybook 10
## Setup
- Storybook 10 is ESM-only. `.storybook/main.ts` and `.storybook/preview.ts` use `import` / `export default`; no `require()` or `module.exports`. It needs Node 20.16+, 22.19+ or 24+.
- Use the framework package that matches the app (`@storybook/react-vite`, `@storybook/nextjs-vite`, `@storybook/vue3-vite`, `@storybook/sveltekit`, `@storybook/angular`) and import `Meta` / `StoryObj` types from it.
- Global providers (theme, router, i18n, query client) go in `preview.ts` decorators. Add per-story decorators only for exceptions.
## Writing stories
- Colocate `Button.stories.tsx` next to `Button.tsx`. Let Storybook derive the sidebar title from the path; don't set `title` unless the repo already does.
- Default to CSF 3:
```ts
const meta = { component: Button, args: { label: 'Save' } } satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Disabled: Story = { args: { disabled: true } };
```
Use CSF Factories (`preview.meta()` / `meta.story()`) only if the repo already uses them. Never mix formats in one file.
- One story per meaningful state: default, each variant, disabled, loading, empty, error, long text or overflow. Name stories after the state (`Loading`), never `Story1`.
- Put shared args on `meta`; each story overrides only what differs. Add `argTypes` only for controls Storybook can't infer.
- Pass handlers as `fn()` from `storybook/test` (`args: { onClick: fn() }`) so they show in the Actions panel and can be asserted.
- Stories never call real APIs. Mock requests with MSW if the repo uses `msw-storybook-addon`, or register module mocks with `sb.mock(...)` in `.storybook/preview.ts`. Keep fixtures in a `__fixtures__` or `mocks/` folder, not inline in every story.
- Add `tags: ['autodocs']` on `meta` when the component needs a docs page. Use MDX only for prose pages, embedding stories with `<Canvas of={...} />`.
## Interaction tests
- Write tests as `play` functions: `play: async ({ canvas, userEvent, args }) => { ... }`.
- Import `expect`, `fn` and `waitFor` from `storybook/test` (the old `@storybook/test` package is pre-9).
- Query like a user: `canvas.getByRole('button', { name: /save/i })`, `getByLabelText`. No CSS selectors or test ids unless there's no accessible name.
- `await` every `userEvent` call and every `expect`. Assert on visible output and on handler calls (`await expect(args.onSubmit).toHaveBeenCalledWith(...)`), not on component internals.
## Accessibility
- Keep `@storybook/addon-a11y` on. Fix violations in the component instead of disabling rules. If one story has a known issue, scope the exception to that story with a comment explaining why.
## Commands
- Dev: `npm run storybook` · Static build: `npm run build-storybook`
- Tests: `npx vitest --project=storybook` (install the runner with `npx storybook add @storybook/addon-vitest`)
- Upgrade: `npx storybook@latest upgrade`
## Done when
New and changed stories render without console errors, `vitest --project=storybook` passes, and the a11y panel shows no new violations.