Skip to main content

Overview

Storybook is the official development environment for the Conty Design System. Every component is born, tested, and validated in Storybook before being used in production.
Stories are not just documentation - they are executable test cases covering rendering, interaction, accessibility, and visual regression.

Getting Started

Start the Storybook development server:
npm run storybook
Storybook will open at http://localhost:6006.

Component Story Format 3 (CSF3)

We use CSF3 for all stories. This format provides better TypeScript support and cleaner syntax.

Basic Story Structure

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'outline'],
    },
  },
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    children: 'Primary Button',
    variant: 'primary',
  },
};

export const Secondary: Story = {
  args: {
    children: 'Secondary Button',
    variant: 'secondary',
  },
};

Meta Configuration

The Meta object configures your component’s stories:
  • title: Navigation path in Storybook sidebar
  • component: The React component being documented
  • tags: Add 'autodocs' to generate automatic documentation
  • argTypes: Control types for interactive props
  • decorators: Wrappers for stories (providers, themes, etc.)
  • parameters: Story-level configuration

Reusable Args

Define common args at the meta level to reduce repetition:
const meta: Meta<typeof Input> = {
  title: 'Forms/Input',
  component: Input,
  args: {
    // Default args for all stories
    placeholder: 'Enter text...',
  },
};

export const Default: Story = {}; // Uses default args

export const WithLabel: Story = {
  args: {
    label: 'Email',
    type: 'email',
    // Inherits placeholder from meta
  },
};

Decorators

Decorators wrap stories with providers, themes, or layout containers.

Story-Level Decorator

export const WithTheme: Story = {
  args: {
    children: 'Themed Button',
  },
  decorators: [
    (Story) => (
      <div className="dark" style={{ padding: '2rem' }}>
        <Story />
      </div>
    ),
  ],
};

Component-Level Decorator

const meta: Meta<typeof Dialog> = {
  title: 'Overlays/Dialog',
  component: Dialog,
  decorators: [
    (Story) => (
      <div style={{ minHeight: '400px' }}>
        <Story />
      </div>
    ),
  ],
};

Global Decorators

Global decorators are configured in .storybook/preview.tsx for theme providers, i18n, and other app-wide context:
// .storybook/preview.tsx
import { ThemeProvider } from 'next-themes';

export const decorators = [
  (Story) => (
    <ThemeProvider attribute="class" defaultTheme="light">
      <Story />
    </ThemeProvider>
  ),
];
Global decorators apply to all stories automatically. Use them for app-wide providers like theme, i18n, or routing.

Play Tests

Play tests enable interaction testing within Storybook. They run automatically and validate component behavior.

Basic Play Test

import { within, userEvent, expect } from '@storybook/test';

export const ClickInteraction: Story = {
  args: {
    children: 'Click me',
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    await userEvent.click(button);
    await expect(button).toHaveFocus();
  },
};

Multi-Step Play Test

export const FormValidation: Story = {
  args: {
    onSubmit: fn(),
  },
  play: async ({ canvasElement, step }) => {
    const canvas = within(canvasElement);
    
    await step('Fill out the form', async () => {
      const emailInput = canvas.getByLabelText('Email');
      await userEvent.type(emailInput, '[email protected]');
      
      const passwordInput = canvas.getByLabelText('Password');
      await userEvent.type(passwordInput, 'securePassword123');
    });
    
    await step('Submit the form', async () => {
      const submitButton = canvas.getByRole('button', { name: /submit/i });
      await userEvent.click(submitButton);
    });
    
    await step('Verify submission', async () => {
      await expect(canvas.getByText('Success!')).toBeInTheDocument();
    });
  },
};

Testing Async Behavior

import { waitFor } from '@storybook/test';

export const LoadingState: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    await userEvent.click(button);
    
    // Wait for loading state
    await waitFor(() => {
      expect(canvas.getByText('Loading...')).toBeInTheDocument();
    });
    
    // Wait for loaded state
    await waitFor(
      () => {
        expect(canvas.getByText('Data loaded')).toBeInTheDocument();
      },
      { timeout: 3000 }
    );
  },
};
Play tests run in real browsers using @storybook/addon-vitest and Playwright, ensuring high-fidelity interaction testing.

Running Tests

In Storybook UI

Play tests run automatically when you view a story. Check the “Interactions” panel to see test results.

In CI

Run component tests in CI:
# Run all Storybook tests
vitest --project=storybook

# Build Storybook with tests
npm run build-storybook -- --test
Storybook tests run in a browser environment with Playwright, providing realistic interaction testing.

Accessibility Testing

Storybook includes the accessibility addon (@storybook/addon-a11y) which automatically checks stories for violations.

Viewing A11y Results

  1. Open a story in Storybook
  2. Click the “Accessibility” tab in the addons panel
  3. Review any violations
  4. Fix critical and serious issues before merging

Configuring A11y Checks

export const CustomA11y: Story = {
  parameters: {
    a11y: {
      config: {
        rules: [
          {
            id: 'color-contrast',
            enabled: true,
          },
        ],
      },
    },
  },
};
Components with critical accessibility violations will not be merged. Always check the Accessibility tab before submitting a PR.

Visual Regression Testing

Visual regression ensures components look correct across browsers and prevents unintended styling changes.

Setup

Visual regression is typically handled by Chromatic or equivalent tooling in CI.

Best Practices

  • Create stories for all visual states
  • Use consistent viewport sizes
  • Test both light and dark themes
  • Include responsive breakpoints

Example: Multiple Viewports

export const Responsive: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'mobile1',
    },
  },
};

export const Desktop: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'desktop',
    },
  },
};

MCP Integration

The Storybook MCP addon (@storybook/addon-mcp) is enabled and provides an endpoint for AI agents.

Validate MCP

Check that MCP is working:
curl http://localhost:6006/mcp
AI agents should consult the MCP endpoint before generating or modifying UI stories. Include MCP validation in your local setup checklist.

Parameters

Parameters customize story behavior and appearance.

Common Parameters

export const WithCustomParams: Story = {
  parameters: {
    // Layout
    layout: 'centered', // 'centered' | 'fullscreen' | 'padded'
    
    // Backgrounds
    backgrounds: {
      default: 'dark',
      values: [
        { name: 'dark', value: '#1a1a1a' },
        { name: 'light', value: '#ffffff' },
      ],
    },
    
    // Actions
    actions: {
      disable: true,
    },
    
    // Documentation
    docs: {
      description: {
        story: 'This story demonstrates custom parameters.',
      },
    },
  },
};

Development Workflow

1

Create component file

Start with a basic component implementation in packages/ui/src/.
2

Create story file

Add a .stories.tsx file next to your component.
3

Write basic stories

Create stories for default state and common variations.
4

Add play tests

Implement interaction tests for critical behaviors.
5

Check accessibility

Review the Accessibility tab and fix violations.
6

Verify tokens

Ensure all visual values use design tokens.
7

Document usage

Add descriptions and documentation to your stories.

Best Practices

Story Naming

  • Use descriptive names: WithIcon, Loading, Disabled
  • Group related stories with naming conventions
  • Avoid generic names like Story1, Test

Story Organization

// States
export const Default: Story = { /* ... */ };
export const Disabled: Story = { /* ... */ };
export const Loading: Story = { /* ... */ };
export const Error: Story = { /* ... */ };

// Variants
export const Primary: Story = { /* ... */ };
export const Secondary: Story = { /* ... */ };
export const Outline: Story = { /* ... */ };

// Sizes
export const Small: Story = { /* ... */ };
export const Medium: Story = { /* ... */ };
export const Large: Story = { /* ... */ };

// Complex scenarios
export const WithIcon: Story = { /* ... */ };
export const WithLongText: Story = { /* ... */ };

Coverage as a Barometer

Use test coverage as a risk indicator, not a target:
  • Don’t chase 100% coverage blindly
  • Focus on critical paths and edge cases
  • Use coverage gaps to identify untested scenarios

Performance

For fast CI pipelines, consider:
npm run build-storybook -- --test
This builds Storybook with tests included, optimizing CI performance.

Continuous Publishing

Storybook should be published continuously for review:
  • Designers can review visual implementation
  • Product can validate features
  • Historical versions serve as documentation

Examples

Review existing component stories in the Storybook for real-world examples:
  • Button: Basic interactions and states
  • Input: Form validation and accessibility
  • Dialog: Complex interactions and focus management
  • Select: Keyboard navigation and ARIA patterns

Troubleshooting

Stories not appearing

  • Check the title path in your meta configuration
  • Ensure the file ends with .stories.tsx
  • Restart the Storybook server

Play tests failing

  • Check the Interactions panel for error details
  • Verify element selectors (use getByRole when possible)
  • Add waitFor for async operations

Accessibility violations

  • Review the Accessibility tab for specific issues
  • Check color contrast ratios
  • Verify ARIA attributes and semantic HTML
  • Test keyboard navigation manually

Build docs developers (and LLMs) love