Skip to content

Fix: Storybook Not Working — Addon Conflicts, Component Not Rendering, or Build Fails After Upgrade

FixDevs ·

Quick Answer

How to fix Storybook issues — CSF3 story format, addon configuration, webpack vs Vite builder, decorator setup, args not updating component, and Storybook 8 migration problems.

The Problem

Storybook starts but a component doesn’t render — just a blank canvas:

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

const meta: Meta<typeof Button> = {
  component: Button,
};
export default meta;

export const Primary: StoryObj<typeof Button> = {};
// Canvas is blank — no error, just empty

Or args don’t update the component when controls are changed:

export const Configurable: StoryObj<typeof Button> = {
  args: {
    label: 'Click me',
    disabled: false,
  },
};
// Changing 'label' in the Controls panel doesn't update the rendered button

Or Storybook fails to build after upgrading:

npx storybook@latest upgrade
# Module not found: Error: Can't resolve '@storybook/addon-actions/dist/esm/preset'
# Error: Cannot find module 'storybook/internal/preview-api'

Or an addon conflicts with the Vite builder:

# info => Loading presets
# error => Error: addons-essentials: Cannot find module '@storybook/addon-links'

Why This Happens

Storybook has evolved significantly and has several compatibility requirements:

  • Blank canvas with no error — the component renders nothing either because required props aren’t provided via args, the component has an unhandled null check, or a CSS import fails silently.
  • Args not updating — components that don’t destructure their props from the Storybook-provided args won’t react to control changes. The component must accept and use the props passed by Storybook.
  • Version mismatches between addons — all @storybook/* packages must be on the same major version. Mixing Storybook 7 and 8 packages causes import errors.
  • Vite builder incompatibility — some older addons were written for webpack and don’t work with @storybook/builder-vite. Check addon compatibility before using them.

Fix 1: Write Stories Correctly with CSF3

Component Story Format 3 (CSF3) is the current standard:

// Button.tsx
interface ButtonProps {
  label: string;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
  onClick?: () => void;
}

export function Button({ label, variant = 'primary', disabled = false, onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

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

// Meta — defines the component and default settings
const meta = {
  title: 'Components/Button',  // Story sidebar path (optional)
  component: Button,
  parameters: {
    layout: 'centered',  // 'centered' | 'fullscreen' | 'padded'
  },
  tags: ['autodocs'],  // Generate automatic documentation
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary'],
    },
    onClick: { action: 'clicked' },  // Show action in Actions panel
  },
  args: {
    label: 'Button',  // Default args for ALL stories in this file
  },
} satisfies Meta<typeof Button>;

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

// Minimal story — uses default args
export const Primary: Story = {};

// Story with overrides
export const Secondary: Story = {
  args: {
    variant: 'secondary',
  },
};

// Story with all args
export const Disabled: Story = {
  args: {
    label: 'Disabled Button',
    disabled: true,
  },
};

// Story with render function for complex scenarios
export const WithCallback: Story = {
  args: {
    label: 'Click me',
  },
  render: (args) => (
    <div>
      <Button {...args} />
      <p>Click the button above</p>
    </div>
  ),
};

// Story with play function (interaction testing)
import { userEvent, within } from '@storybook/test';

export const Interactive: Story = {
  args: { label: 'Submit' },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button', { name: 'Submit' });
    await userEvent.click(button);
    // args.onClick will have been called
  },
};

Fix 2: Fix Args Not Updating the Component

Components must use the props provided by Storybook’s args:

// WRONG — component doesn't use props, always shows 'Click me'
export function Button() {
  return <button>Click me</button>;
}

// ALSO WRONG — using hardcoded values instead of props
export function Button(props: ButtonProps) {
  const label = 'hardcoded';  // Ignoring props.label
  return <button>{label}</button>;
}

// CORRECT — always use the props passed in
export function Button({ label, variant = 'primary', onClick }: ButtonProps) {
  return (
    <button className={`btn-${variant}`} onClick={onClick}>
      {label}
    </button>
  );
}

// CORRECT — story provides args that are passed as props
export const Primary: Story = {
  args: {
    label: 'Submit',  // Passed as the 'label' prop
    variant: 'primary',
  },
};

Context-dependent components — use decorators:

// Component that needs a provider
export function UserAvatar() {
  const { user } = useAuth();  // Uses React context
  return <img src={user.avatar} alt={user.name} />;
}

// Story — wrap with the required provider
export const LoggedIn: Story = {
  decorators: [
    (Story) => (
      <AuthProvider value={{ user: { name: 'Alice', avatar: '/alice.jpg' } }}>
        <Story />
      </AuthProvider>
    ),
  ],
};

// Or set global decorators in .storybook/preview.ts
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
import { ThemeProvider } from '../src/ThemeProvider';

const preview: Preview = {
  decorators: [
    (Story) => (
      <ThemeProvider theme="light">
        <Story />
      </ThemeProvider>
    ),
  ],
  parameters: {
    controls: { matchers: { color: /(background|color)$/i } },
  },
};

export default preview;

Fix 3: Fix Storybook After Upgrading

Upgrade all Storybook packages together — mixing versions causes errors:

# Upgrade everything to the latest version automatically
npx storybook@latest upgrade

# If the automatic upgrade fails, upgrade manually
# First, remove all @storybook/* packages
npm uninstall $(node -e "
  const pkg = require('./package.json');
  const deps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
  console.log(deps.filter(d => d.startsWith('@storybook') || d === 'storybook').join(' '));
")

# Reinstall with the target version
npx storybook@8 init  # Or: npx sb@8 init

# Fix addon compatibility issues
npx storybook@latest automigrate

Common Storybook 8 migration changes:

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

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-onboarding',
    '@storybook/addon-interactions',
    // Addon packages must match the installed Storybook version
  ],
  framework: {
    name: '@storybook/react-vite',  // Or @storybook/react-webpack5
    options: {},
  },
};

export default config;

Fix package version mismatches:

# Check all @storybook packages and their versions
npm ls | grep @storybook

# They should all be on the same major version (e.g., 8.x.x)
# If you see mixed versions, reinstall

# package.json — pin all to the same version
{
  "devDependencies": {
    "@storybook/addon-essentials": "^8.0.0",
    "@storybook/addon-interactions": "^8.0.0",
    "@storybook/addon-links": "^8.0.0",
    "@storybook/react": "^8.0.0",
    "@storybook/react-vite": "^8.0.0",
    "@storybook/test": "^8.0.0",
    "storybook": "^8.0.0"
  }
}

Fix 4: Configure Vite and Webpack Builders

// .storybook/main.ts — Vite builder configuration
import type { StorybookConfig } from '@storybook/react-vite';
import { mergeConfig } from 'vite';

const config: StorybookConfig = {
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  viteFinalConfig: async (config) => {
    // Merge with your Vite config
    return mergeConfig(config, {
      resolve: {
        alias: {
          '@': '/src',  // Path aliases
        },
      },
      define: {
        'process.env.NODE_ENV': '"development"',
      },
    });
  },
};

// .storybook/main.ts — Webpack builder configuration
import type { StorybookConfig } from '@storybook/react-webpack5';

const config: StorybookConfig = {
  framework: {
    name: '@storybook/react-webpack5',
    options: {},
  },
  webpackFinal: async (config) => {
    // Add custom webpack rules
    config.module?.rules?.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
    });

    // Add path aliases
    if (config.resolve) {
      config.resolve.alias = {
        ...config.resolve.alias,
        '@': path.resolve(__dirname, '../src'),
      };
    }

    return config;
  },
};

Fix Tailwind CSS not applying in Storybook:

// .storybook/preview.ts
import '../src/index.css';  // Import your global CSS with Tailwind directives
// import '../src/styles/globals.css';  // Or wherever your Tailwind is

// Ensure Tailwind processes Storybook files
// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,jsx,ts,tsx}',
    './.storybook/**/*.{js,jsx,ts,tsx}',  // Include Storybook files
  ],
  // ...
};

Fix 5: Mock Modules and External Dependencies

// .storybook/preview.ts — mock modules globally
import { fn } from '@storybook/test';

// Mock a module for all stories
const preview: Preview = {
  parameters: {
    // Mock fetch/API calls
    mockAddonConfig: { disable: false },
  },
};

// Mock in individual stories
import * as RouterModule from 'react-router-dom';

export const WithNavigation: Story = {
  parameters: {
    // Storybook Router addon
    reactRouter: {
      routePath: '/users/:id',
      routeParams: { id: '1' },
    },
  },
};

// Mock a hook
import { useRouter } from 'next/navigation';

jest.mock('next/navigation', () => ({
  useRouter: () => ({
    push: fn(),
    back: fn(),
    pathname: '/users',
  }),
}));

// Or use decorator pattern to inject mock context
export const WithMockedRouter: Story = {
  decorators: [
    (Story) => (
      <RouterContext.Provider value={{
        push: fn(),
        pathname: '/test',
        query: {},
        asPath: '/test',
      } as NextRouter}>
        <Story />
      </RouterContext.Provider>
    ),
  ],
};

Fix 6: Test Stories with play Functions

// Use play functions for interaction testing
import { userEvent, within, expect } from '@storybook/test';

export const LoginForm: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Fill in the form
    await userEvent.type(
      canvas.getByLabelText('Email'),
      '[email protected]'
    );
    await userEvent.type(
      canvas.getByLabelText('Password'),
      'password123'
    );

    // Submit
    await userEvent.click(canvas.getByRole('button', { name: 'Sign In' }));

    // Assert the result
    await expect(
      canvas.getByText('Welcome back, Alice!')
    ).toBeInTheDocument();
  },
};

// Run interaction tests in CI
// package.json
{
  "scripts": {
    "test-storybook": "test-storybook --url http://localhost:6006",
    "storybook": "storybook dev -p 6006"
  }
}
# Install and run Storybook test runner
npm install --save-dev @storybook/test-runner

# Run all story interaction tests
npx test-storybook

# Run in CI (start Storybook first)
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
  "npx storybook dev --port 6006 --quiet" \
  "npx wait-on tcp:6006 && npx test-storybook"

Still Not Working?

Component renders but styles are missing — Storybook has its own bundle separate from your app. CSS imports must be added to .storybook/preview.ts. Check: global CSS, CSS modules (usually work automatically), and third-party CSS libraries (may need to be imported explicitly in preview).

@storybook/test vs @testing-library/react conflicts — Storybook 8 ships its own @storybook/test which re-exports Jest/Testing Library APIs. If you import from both, you may get conflicting versions. Prefer @storybook/test for interactions inside stories, and keep @testing-library/react for Jest/Vitest unit tests.

MSW integration for API mocking in Storybook — use storybook-addon-msw or msw-storybook-addon to mock API calls in stories. Initialize the Service Worker in .storybook/preview.ts using the same worker.start() pattern as your app. Stories can then define handlers in their parameters.msw.handlers property.

For related frontend testing issues, see Fix: MSW Not Working and Fix: Jest Setup File Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles