Storybook Guidelines (React)
Overview
Storybook is used for:
- Component development in isolation
- Visual documentation of component states
- Interaction testing via play functions
- Accessibility auditing
- Visual regression testing
Story File Structure
Meta Configuration
import type { Meta, StoryObj } from '@storybook/react';
import { MyComponent } from './MyComponent';
/**
* Test Plan: MyComponent
*
* Scenario: Default rendering
* Given the component is mounted with default props
* Then it should display correctly
*
* Scenario: User interaction
* Given the component is mounted
* When the user clicks the button
* Then the state should update
*/
const meta: Meta<typeof MyComponent> = {
title: 'Category/Subcategory/MyComponent',
component: MyComponent,
parameters: { layout: 'centered' },
tags: ['autodocs'],
argTypes: {
variant: { control: 'select', options: ['primary', 'secondary', 'danger'] },
},
};
export default meta;
type Story = StoryObj<typeof meta>;
Story Title Organization
// Pattern: 'Category/Subcategory/ComponentName'
title: 'Components/Forms/TextInput'
title: 'Views/Dashboard/Overview'
title: 'Primitives/Controls/Button'
Basic Stories
export const Default: Story = {
args: { label: 'Click me', variant: 'primary' },
};
export const Secondary: Story = {
args: { ...Default.args, variant: 'secondary' },
};
// With custom render
export const WithIcon: Story = {
args: { label: 'Save', icon: 'save' },
render: (args) => (
<div style={{ padding: '20px' }}>
<MyComponent {...args} />
</div>
),
};
Interaction Tests with play()
Basic Structure
import { expect, userEvent, within, waitFor } from '@storybook/test';
export const Interactive: Story = {
args: { label: 'Submit' },
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
await step('Click the button', async () => {
const button = canvas.getByRole('button', { name: 'Submit' });
await userEvent.click(button);
});
await step('Verify state change', async () => {
await waitFor(() => {
expect(canvas.getByText('Submitted')).toBeInTheDocument();
});
});
},
};
Query Strategies
Component-scoped queries:
const canvas = within(canvasElement);
const button = canvas.getByRole('button', { name: 'Submit' });
Global queries (for modals, toasts, dropdowns):
import { screen } from '@storybook/test';
const modal = screen.getByRole('dialog');
const toast = screen.getByRole('status');
Testing Library Query Priority
For Testing Library query priority, see vitest-guidelines.
Async Handling
Always use waitFor for async assertions:
await waitFor(() => {
expect(canvas.getByText('Success')).toBeInTheDocument();
});
Check for element removal:
await waitFor(() => {
expect(canvas.queryByRole('alert')).not.toBeInTheDocument();
});
Best Practices
Story Naming and Organization
// Good - descriptive states; visual states first, interactive tests last
export const Default: Story = { ... };
export const Disabled: Story = { ... };
export const WithError: Story = { ... };
export const Loading: Story = { ... };
export const UserFlow: Story = {
play: async ({ canvasElement, step }) => { ... },
};
// Bad - vague or numbered
export const Story1: Story = { ... };
export const Test: Story = { ... };
Test Plan Alignment
Every story with a play() function should have a corresponding test plan (see Meta Configuration example above for format).
Common Pitfalls
- Missing waitFor — Always wrap async assertions in
waitFor()after user interactions to avoid race conditions - Wrong query scope — Use
within(canvasElement)for component queries; usescreenonly for teleported elements (modals, toasts) - Toast vs Alert roles — Success notifications use
role="status", errors userole="alert" - Global queries in component scope — Modals/dropdowns are teleported outside the component; query them via
screen, notcanvas
Running Storybook Tests
npm run storybook # Start Storybook
npm run test-storybook # Run all story tests
npm run test-storybook -- --grep "ComponentName" # Run specific tests
npm run test-storybook -- --coverage # With coverage
Additional References
- Common Interactions — Button clicks, text input, checkboxes, selects, keyboard interactions
- Mocking Patterns — MSW API mocking and store/state mocking with decorators
- Story Parameters — Layout, backgrounds, viewport, and control parameters
Converted and distributed by TomeVault — claim your Tome and manage your conversions.