Storybook Stories
Overview
Storybook is a frontend workshop for building UI components in isolation. Stories are written in Component Story Format 3 (CSF3), which uses object syntax with args for type-safe component documentation and testing.
When to use: Component documentation with live examples, interaction testing with play functions, visual regression testing, accessibility validation, design system maintenance, isolated component development.
When NOT to use: End-to-end testing (use Playwright/Cypress), API integration testing (use Vitest/Jest), full application testing (use browser automation), performance testing (use Lighthouse/WebPageTest).
Quick Reference
| Pattern | API | Key Points |
|---|---|---|
| Basic story | export const Default: Story = { args } | Use args for simple single components |
| Complex story | export const Example: Story = { render } | Use render for multi-component layouts |
| Meta configuration | const meta = { component, args } satisfies Meta | Define defaults and argTypes |
| Interaction test | play: async ({ canvas, userEvent, args }) | canvas and userEvent provided directly |
| User interaction | await userEvent.click(element) | Always await userEvent methods |
| Query elements | canvas.getByRole('button') | Prefer getByRole over other queries |
| Assertions | await expect(args.onPress).toHaveBeenCalled() | Use storybook/test assertions |
| beforeEach hook | beforeEach: async ({ args }) => {} | Setup mocks before story renders |
| Play composition | await OtherStory.play?.(context) | Reuse setup across stories |
| Autodocs | tags: ['autodocs'] | Enable automatic documentation |
| Controls customization | argTypes: { variant: { control: 'select' } } | Configure control panel |
| Decorators | decorators: [withTheme] | Add wrappers or context providers |
| Parameters | parameters: { layout: 'centered' } | Configure addon behavior per story |
| Chromatic snapshot | parameters: { chromatic: { delay: 300 } } | Control visual regression captures |
| Disable snapshot | parameters: { chromatic: { disableSnapshot: true } } | Skip story in visual tests |
| A11y testing | await expect(button).toHaveAccessibleName() | Validate accessible labels |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
| Using args with multi-component layouts | Use render for complex compositions |
| Not awaiting userEvent methods | Always await: await userEvent.click(button) |
| Using within(canvasElement) manually | Destructure canvas from play context directly |
| Using getByTestId first | Prefer getByRole, getByLabelText, getByText |
| Missing default args at meta level | Add args to meta to prevent placeholder controls |
| Exposing non-serializable props | Disable className, ref, style in argTypes |
| Not composing play functions | Reuse setup with await BaseStory.play?.(context) |
| Forgetting to mock callbacks | Use fn() for event handlers: args: { onPress: fn() } |
| Missing accessible names for icon buttons | Add aria-label or use aria-labelledby |
| Not scoping queries for portal content | Use within(canvasElement.ownerDocument.body) |
| Using boolean && for conditional rendering | Use ternary in stories for consistent snapshots |
| Not waiting for animations | Wrap assertions in waitFor for async state |
Common Fixes
| Problem | Solution |
|---|---|
| Controls show placeholders | Add args at meta level with default values |
| Serialization error | Disable className, ref, style in argTypes |
| Portal element not found | Search body instead of canvas |
| Animation timing issues | Wrap assertions in waitFor |
| Multiple buttons found | Add { name: '...' } to getByRole |
| A11y test failing | Add label prop or aria-label |
Delegation
- Story structure review: Use
code-reviewerskill for CSF3 pattern validation - Accessibility audit: Use
Exploreagent to discover ARIA patterns across stories - Visual regression analysis: Use
Taskagent to investigate Chromatic failures