Fix: Storybook Not Working — Addon Conflicts, Component Not Rendering, or Build Fails After Upgrade
Part of: React & Frontend Errors
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 7 and 8 are fundamentally different from Storybook 6, and the most common breakage comes from leftover assumptions from older docs.
The first axis is the builder. Storybook now ships separate @storybook/react-vite, @storybook/react-webpack5, @storybook/nextjs, and similar framework packages. Older addons written against the Webpack 5 builder import internal paths that don’t exist in the Vite builder, which is why you sometimes see Cannot find module '@storybook/addon-actions/dist/esm/preset' despite the addon being installed. The fix is almost never “delete cache” — it is matching the framework package to your project bundler and pruning addons that haven’t been updated for the Vite path.
The second axis is version drift across @storybook/* packages. All of them must be on the same major (and ideally the same minor) version. npm and pnpm hoist transitive copies aggressively, so a single stale entry like @storybook/[email protected] next to [email protected] will break preset resolution with errors that look unrelated. npx storybook@latest upgrade and the bundled automigrate codemods fix the bulk of it.
The third axis is the story itself. A blank canvas with no error usually means the component renders nothing because required props aren’t being provided via args, the component reads from a context that isn’t wrapped by a decorator, or a global CSS import is missing from .storybook/preview.ts. Args not updating is almost always a component that hardcodes a value instead of consuming the prop Storybook is feeding it.
Diagnostic Timeline
Minute 0 — Blank canvas, no error. Your first instinct is to delete node_modules/.cache/storybook and restart. It almost never helps. Open the browser devtools and check the iframe at /iframe.html?id=your-story-id directly. If you see the story container but nothing inside, the component is rendering and returning null — a prop issue. If you see a hydration error, an addon decorator is wrapping the story with a provider that throws.
Minute 3 — Check the framework. Open .storybook/main.ts and look at framework.name. If it says @storybook/react (no -vite or -webpack5 suffix), you’re on an outdated config — Storybook 7+ requires the framework-builder combination. Switch to @storybook/react-vite for Vite projects or @storybook/react-webpack5 for CRA/webpack projects.
Minute 6 — Audit addon versions. Run npm ls @storybook/addon-essentials @storybook/addon-links storybook and look for mismatched majors. If you see 7.x and 8.x side by side, that’s the failure. Pin every @storybook/* package to the same major in package.json, delete node_modules plus the lockfile, and reinstall.
Minute 10 — Run automigrate. npx storybook@latest automigrate detects most addon-essentials conflicts, missing addon-onboarding, removed storyStoreV7 flags, and the CSF3 migration. Read the diff before accepting it — automigrate sometimes rewrites main.ts aggressively.
Minute 15 — Inspect args binding. If the component renders but controls don’t update it, open the story file and confirm the component destructures the prop name from its arguments. A <Button label="Click" /> that hardcodes Click inside the component will never react to the Controls panel no matter what args say.
Minute 20 — Check preview.ts CSS imports. If styles are missing, Storybook needs your global CSS imported explicitly in .storybook/preview.ts. Tailwind needs ./.storybook/**/* added to the content glob in tailwind.config.js, otherwise classes used only inside stories get tree-shaken.
Minute 30 — Reinstall from scratch. If automigrate didn’t converge, the cleanest reset is rm -rf node_modules package-lock.json and npx storybook@latest init --force on a fresh branch. Compare the generated .storybook/main.ts against yours to spot the divergence.
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). For Tailwind, add ./.storybook/**/*.{js,ts} and ./src/**/*.{js,ts,jsx,tsx} to the content array — story-only utility classes get purged otherwise.
@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.
Vite dev server hangs on first start after upgrade — Storybook’s Vite builder pre-bundles every story file the first time it boots, which can take 60+ seconds on a large project. If it appears stuck, give it three minutes before killing it. The next start uses the dep cache and finishes in seconds. If it still hangs, delete node_modules/.vite-storybook and let it rebuild once.
Story file matches glob but doesn’t appear in the sidebar — Storybook discovers stories via the stories array in .storybook/main.ts. Common gotchas: the glob doesn’t include the file extension you use (.mjs is not in the default), the file doesn’t have a default Meta export, or the file exports a non-story value at the top level that crashes the loader. Check the terminal for a “Failed to load story” warning.
Decorator renders but story body is empty — a global decorator in preview.ts that returns <Wrapper><Story /></Wrapper> only works if Story is rendered. If you instead return <Wrapper>{Story()}</Wrapper> (calling Story as a function), some versions of Storybook lose the decorator context. Always render <Story /> as a JSX element, not as a function call.
For related frontend testing issues, see Fix: MSW Not Working, Fix: Jest Setup File Not Working, Fix: Vite Failed to Resolve Import, and Fix: Playwright Component Testing 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.