From addg
Storybook story authoring guidelines — when to create stories, variant policy, file placement, title convention, decorators, MSW mocking, and mock data. Use when writing or reviewing Storybook stories, deciding which variants to add, configuring Next.js navigation in stories, or mocking API calls with MSW.
How this skill is triggered — by the user, by Claude, or both
Slash command
/addg:storybook-storiesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Stories serve two goals:
Stories serve two goals:
Create a story for:
modules/shared/)ProposalsSection, WalletsSection, RoadmapSwiper)Do not create a story for:
The goal is maximum regression coverage with minimum story files. When in doubt, prefer one story on the parent component over several stories on its children.
Only create story variants that produce visually distinct UI states relevant for regression detection:
| Good variant | Reason |
|---|---|
Default | The typical happy-path rendering |
Loading | Skeleton or spinner state (visually distinct) |
Empty | Zero-data state (different layout) |
Error | Error state (different UI) |
| Data-shape variants | E.g., SingleProposal vs many — affects layout |
Avoid:
Aim for the minimum set that exercises all meaningfully different UI states.
Colocate the story file with the component it documents:
modules/networks/components/proposals-section/
proposals-section.tsx
proposals-section.stories.tsx ← here
mocks/
proposals.ts
File name: [component-name].stories.tsx
Always use Component Story Format (CSF 3) with TypeScript:
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { MyComponent } from './my-component'
const meta = {
title: 'Modules/Networks/Components/MyComponent',
component: MyComponent,
parameters: {
layout: 'fullscreen', // 'centered' for isolated UI components
},
} satisfies Meta<typeof MyComponent>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
// ...
},
}
Always import from @storybook/nextjs-vite, not @storybook/react.
Document the component's args in the meta via argTypes, including their types and descriptions. This improves the Storybook Controls panel and serves as inline documentation for developers.
const meta = {
title: 'Modules/Example/Components/MyComponent',
component: MyComponent,
argTypes: {
variant: {
control: 'select',
options: ['default', 'outline', 'ghost'],
description: 'Visual style variant of the component',
},
disabled: {
control: 'boolean',
description: 'Whether the component is disabled',
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
description: 'Size of the component',
},
},
} satisfies Meta<typeof MyComponent>
control (e.g. 'boolean', 'select', 'text') and optionally options for enumsdescription string for each arg to explain its purposeargTypes over inline comments — they surface in Storybook's docs and Controls| Component type | Title pattern | Example |
|---|---|---|
| Shared UI component | Shared/{Category}/{ComponentName} | Shared/Shadcn/Button |
| Domain component | Modules/{Module}/Components/{ComponentName} | Modules/Networks/Components/ProposalsSection |
| Nested domain component | Modules/{Module}/Components/{Parent}/{ComponentName} | Modules/Networks/Components/GovernanceSection/ForumOverview |
parameters: {
layout: 'fullscreen', // sections, pages, full-width components
layout: 'centered', // isolated UI components (buttons, cards, inputs)
layout: 'padded', // components that need some surrounding space
}
Import shared decorators from modules/shared/lib/decorators.tsx. Apply them at the meta level (affects all stories) or per-story.
import {
withNuqsAdapter,
withReactQueryProvider,
withPortalFontStyles,
} from '@/modules/shared/lib/decorators'
| Decorator | When to use |
|---|---|
withNuqsAdapter | Component reads URL search params via nuqs |
withReactQueryProvider | Component uses TanStack Query (useQuery, useMutation) |
withPortalFontStyles | Component renders portals, tooltips, menus, or overlays |
withNextjsExtras | Applied globally — adds fonts; DO NOT add manually |
The withNextjsExtras and theme decorators are registered globally in .storybook/preview.ts and apply to all stories automatically.
Provider requirements: If a component uses a provider that lives outside the component (e.g. QueryClientProvider, NuqsAdapter, CustomModalProvider, etc), wrap the story with that provider using a decorator. This ensures the component has the same context it would have in the app.
Adding new decorators:
modules/shared/lib/decorators.tsx<module>/lib/decorators.ts (e.g. modules/expense-reports/lib/decorators.ts)All components that perform API calls MUST be mocked to avoid real network requests. This applies to both queries (GET) and mutations/actions (POST, PUT, PATCH, DELETE, etc.). Never let stories hit real APIs.
Use MSW to mock HTTP requests in stories. The global mswLoader is already registered in .storybook/preview.ts.
import { delay, http, HttpResponse } from 'msw'
export const Default: Story = {
parameters: {
msw: {
handlers: [
http.get('/api/my-endpoint', () => HttpResponse.json(mockedData)),
],
},
},
}
// Loading state — delay indefinitely
export const Loading: Story = {
parameters: {
msw: {
handlers: [
http.get('/api/my-endpoint', async () => {
await delay('infinite')
}),
],
},
},
}
// Error state
export const Error: Story = {
parameters: {
msw: {
handlers: [
http.get('/api/my-endpoint', () => HttpResponse.error()),
],
},
},
}
Place mock data in the module's mocks/ directory when it is substantial or reused. Keep small, one-off mock data inline (<10 lines) to reduce friction — avoid moving it to a dedicated file unless it is reused elsewhere.
modules/roadmap/
mocks/
roadmap.ts ← export mockedMilestones, mockedDeliverables, etc.
components/
roadmap-swiper/
roadmap-swiper.stories.tsx ← import from '@/modules/roadmap/mocks'
Keep mock data realistic and representative of production data shapes.
['Option A', 'Option B', 'Option C'])status: 'active')For components that manage internal state (controlled inputs, toggles), use a render function with local useState rather than exposing internal state as args:
function ControlledSearch(args: SearchInputProps) {
const [value, setValue] = useState(args.value ?? '')
return <SearchInput {...args} value={value} onChange={setValue} />
}
export const Default: Story = {
args: { placeholder: 'Search...' },
render: (args) => <ControlledSearch {...args} />,
}
Similarly, for form components using react-hook-form, wrap the story in a local component that sets up the form — do not expose UseFormReturn in args.
tags: ['autodocs']Use tags: ['autodocs'] on any reusable components. Do not use it on pages, sections, or single-use components. The only exception is when the user explicitly requests adding a docstring to a specific story to document certain information — do not infer that this might be needed.
For components that call useSearchParams, usePathname, useRouter, or depends on URL in Server Components set the navigation context:
parameters: {
nextjs: {
appDirectory: true,
navigation: {
pathname: '/networks',
query: { search: '', status: 'active' },
},
},
}
For full documentation of Nextjs Navigation: https://storybook.js.org/docs/get-started/frameworks/nextjs-vite/?renderer=react#nextjs-routing
For full-width components that look wrong in centered layout, use an inline decorator to constrain the width:
decorators: [
(Story) => (
<div style={{ maxWidth: '1312px', width: '100%' }}>
<Story />
</div>
),
],
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { delay, http, HttpResponse } from 'msw'
import { withReactQueryProvider } from '@/modules/shared/lib/decorators'
import { mockedItems } from '@/modules/example/mocks'
import MySection from './my-section'
const meta = {
title: 'Modules/Example/Components/MySection',
component: MySection,
parameters: {
layout: 'fullscreen',
nextjs: { appDirectory: true },
},
decorators: [withReactQueryProvider],
} satisfies Meta<typeof MySection>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
parameters: {
msw: { handlers: [http.get('/api/items', () => HttpResponse.json(mockedItems))] },
},
}
export const Loading: Story = {
parameters: {
msw: { handlers: [http.get('/api/items', async () => { await delay('infinite') })] },
},
}
export const Empty: Story = {
parameters: {
msw: { handlers: [http.get('/api/items', () => HttpResponse.json([]))] },
},
}
export const Error: Story = {
parameters: {
msw: { handlers: [http.get('/api/items', () => HttpResponse.error())] },
},
}
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub yasielcabrera/addg-guidelines-skills --plugin addg