Fix: Storybook Not Working — Addon Conflicts, Component Not Rendering, or Build Fails After Upgrade
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 emptyOr 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 buttonOr 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 automigrateCommon 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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AutoAnimate Not Working — Transitions Not Playing, List Items Not Animating, or React State Changes Ignored
How to fix @formkit/auto-animate issues — parent ref setup, React useAutoAnimate hook, Vue directive, animation customization, disabling for specific elements, and framework integration.
Fix: Blurhash Not Working — Placeholder Not Rendering, Encoding Failing, or Colors Wrong
How to fix Blurhash image placeholder issues — encoding with Sharp, decoding in React, canvas rendering, Next.js image placeholders, CSS blur fallback, and performance optimization.
Fix: Embla Carousel Not Working — Slides Not Scrolling, Autoplay Not Starting, or Thumbnails Not Syncing
How to fix Embla Carousel issues — React setup, slide sizing, autoplay and navigation plugins, loop mode, thumbnail carousels, responsive breakpoints, and vertical scrolling.
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.