storybook-component

star 8

Component-driven development with Storybook 10. Use when creating or documenting React components, writing stories or interaction tests, designing UI variations, or syncing stories after component changes.

recca0120 By recca0120 schedule Updated 4/19/2026

name: storybook-component description: > Component-driven development with Storybook 10. Use when creating or documenting React components, writing stories or interaction tests, designing UI variations, or syncing stories after component changes.

Storybook 10 Component Development

Philosophy

Components are designed in Storybook first, then integrated into the app. Every component gets a .stories.tsx file co-located next to it.

components/
  ChatMessage.tsx
  ChatMessage.stories.tsx
  ChatInput.tsx
  ChatInput.stories.tsx

Story File Template (CSF3)

import type { Meta, StoryObj } from '@storybook/react-vite';
import { fn } from 'storybook/test';
import { MyComponent } from './MyComponent';

const meta = {
  component: MyComponent,
  tags: ['autodocs'],
  // Decorators use design token classes, NEVER inline style with hex
  decorators: [
    (Story) => (
      <div className="bg-bg text-text p-4">
        <Story />
      </div>
    ),
  ],
  // Default args shared across all stories
  args: {
    onClick: fn(),
  },
} satisfies Meta<typeof MyComponent>;

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

Writing Stories — One Story Per State

Each named export = one visual state. Cover: default, edge cases, loading, error, disabled.

export const Default: Story = {
  args: {
    label: 'Click me',
    variant: 'primary',
  },
};

export const Disabled: Story = {
  args: {
    ...Default.args,
    disabled: true,
  },
};

export const LongContent: Story = {
  args: {
    label: 'A very long label that might overflow the button container',
  },
};

export const Empty: Story = {
  args: {
    label: '',
  },
};

Interaction Testing with Play Functions

Use play functions to verify interactive behavior directly in Storybook. This replaces basic render tests — no need to duplicate in vitest.

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

export const SubmitForm: Story = {
  args: {
    onSubmit: fn(),
  },
  play: async ({ args, canvas, userEvent, step }) => {
    await step('Fill in form', async () => {
      await userEvent.type(canvas.getByLabelText('Email'), 'test@example.com');
      await userEvent.type(canvas.getByLabelText('Password'), 'secret123');
    });

    await step('Submit', async () => {
      await userEvent.click(canvas.getByRole('button', { name: 'Submit' }));
    });

    await expect(args.onSubmit).toHaveBeenCalledOnce();
  },
};

Key APIs

API Usage
canvas.getByRole() Preferred — query by accessibility role
canvas.getByLabelText() Form fields with labels
canvas.getByText() Visible text content
canvas.findByRole() Async — waits for element to appear
canvas.queryByRole() Check element absence (returns null)
userEvent.click() Click interaction
userEvent.type() Type into input
userEvent.hover() Hover over element
expect() Jest-compatible assertions
fn() Create spy for callback args
step() Group interactions with labels

Custom Render

When the component needs siblings or wrapper context:

export const WithAlert: Story = {
  args: { message: 'Hello' },
  render: (args) => (
    <div className="chat-container">
      <MyComponent {...args} />
      <footer>Status: connected</footer>
    </div>
  ),
};

Decorators for Providers

Wrap stories with context providers at the meta level:

const meta = {
  component: ChatPanel,
  decorators: [
    (Story) => (
      <SocketProvider value={mockSocket}>
        <Story />
      </SocketProvider>
    ),
  ],
} satisfies Meta<typeof ChatPanel>;

Parameters for Addon Config

export const DarkMode: Story = {
  parameters: {
    backgrounds: { default: 'dark' },
    layout: 'fullscreen', // or 'centered', 'padded'
  },
};

Testing Strategy Split

What to test Where
Visual states & variations Storybook stories (args)
User interactions & UI behavior Storybook play functions
Business logic, hooks, stores Vitest unit tests
Complex async flows Vitest with mocks

Rule: If it's about how the component looks or responds to clicks, put it in Storybook. If it's about logic, put it in vitest.

Storybook Config (React + Vite)

Storybook 10 has essentials and interactions built-in. Only add extra addons as needed.

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(ts|tsx)'],
  framework: '@storybook/react-vite',
  addons: ['@storybook/addon-a11y'],
};

export default config;
// .storybook/preview.ts
import type { Preview } from '@storybook/react-vite';

const preview: Preview = {
  parameters: {
    layout: 'centered',
  },
};

export default preview;

Naming Conventions

  • File: ComponentName.stories.tsx (co-located with component)
  • Meta title: auto-derived from file path, or explicit title: 'Components/ChatMessage'
  • Story names: PascalCase describing the state: Default, Loading, WithError, LongContent

Checklist for New Components

  1. Create component file with props interface
  2. Create .stories.tsx with meta + Default story
  3. Add story variants for each meaningful state
  4. Add play functions for interactive behavior
  5. Verify in Storybook UI (pnpm storybook)
  6. Add vitest tests only for logic not covered by stories
Install via CLI
npx skills add https://github.com/recca0120/code-quest --skill storybook-component
Repository Details
star Stars 8
call_split Forks 2
navigation Branch main
article Path SKILL.md
More from Creator