Storybook
Storybook is a widely adopted open-source tool for developing, documenting, and testing UI components in isolation. It enables developers to create "stories" that represent different states of a component, facilitating visual testing, collaboration, and automated testing. The current stable version is 10.3.5, with major releases roughly once a year and minor releases every eight weeks, preceded by alpha/beta/rc pre-releases. Key differentiators include its extensive addon ecosystem, cross-framework support (React, Vue, Angular, etc.), and emphasis on Component Story Format (CSF) for portable story definitions. It's an essential tool for building and maintaining robust design systems, providing a dedicated environment to explore and showcase component variations.
Common errors
-
Cannot find module 'storybook/internal/common'
cause This error often indicates a module resolution issue, frequently encountered in monorepos, misconfigured Storybook projects, or incorrect paths.fixEnsure your `.storybook` folder and its configuration files (especially `main.ts` or `main.js`) have correct relative paths and explicit file extensions for imports. Check for duplicate Storybook packages or version mismatches in your `node_modules` and `package.json`. Running `npx storybook@latest upgrade --force` and `npm install` can sometimes resolve underlying dependency issues. -
Module build failed (from ./node_modules/babel-loader/lib/index.js): Error: Cannot find module '...min-indent/index.js'
cause Indicates a problem with Storybook's internal Babel or Webpack configuration, often due to corrupted `node_modules`, incompatible Babel presets, or file system path issues.fixDelete your `node_modules` folder and `package-lock.json` (or `yarn.lock`), then reinstall dependencies (`npm install` or `yarn install`). Verify that your Babel configuration (if customized) is compatible with Storybook's version. You can debug Webpack configuration with `storybook dev --debug-webpack`. -
Type errors arise because @Output() EventEmitters in Angular don't align with Storybook's ArgsStoryFn type expectations
cause Angular's `EventEmitter` types for `@Output()` properties often cause type mismatches when directly used as `args` in Storybook stories.fixUse TypeScript utility types like `Omit` or `Partial` when defining the `Meta` type for your Angular components to exclude `EventEmitter` properties or make them optional. Define a helper function to prepare arguments, ensuring only compatible properties are passed to Storybook. -
Extensionless imports in Storybook main config
cause Storybook requires explicit file extensions for relative imports within `.storybook/main.js` or `.storybook/main.ts` to prevent deprecation warnings and ensure proper module resolution, especially with ESM.fixAdd explicit `.js` or `.ts` (or `.mjs`, `.mts`) extensions to all relative imports in your `.storybook/main` file. For example, change `import sharedMain from '../main'` to `import sharedMain from '../main.js'` or `../main.ts`.
Warnings
- breaking Storybook 10 is ESM-only. All configuration files (.storybook/main.js|ts, presets, addons) must be valid ES Modules. This is a significant breaking change for projects still using CommonJS modules for their Storybook configuration.
- breaking Storybook 10 requires Node.js version 20.19+ or 22.12+. Older Node.js versions are not supported.
- gotcha The `component manifest` feature was disabled by default in Storybook v10.3.5. If you rely on this feature, particularly with `@storybook/addon-mcp`, it may appear to be missing or non-functional after upgrading.
- deprecated The `storiesOf` API (Component Story Format 2) is deprecated in favor of Component Story Format 3 (CSF 3) using `Meta` and `StoryObj` exports. While `storiesOf` may still work, new features and best practices are built around CSF 3.
- breaking In Storybook for React Native v10, the `withStorybook` function from `@storybook/react-native/metro/withStorybook` is now a named export, not a default export. This impacts Metro bundler configuration.
Install
-
npm install storybook -
yarn add storybook -
pnpm add storybook
Imports
- Meta
import { Meta } from 'storybook';import type { Meta, StoryObj } from '@storybook/react'; - StoryObj
import { Story } from '@storybook/react';import type { Meta, StoryObj } from '@storybook/react'; - Preview
import { Preview } from 'storybook';import type { Preview } from '@storybook/react'; - StorybookConfig
import { StorybookConfig } from 'storybook';import type { StorybookConfig } from '@storybook/react-vite'; - action
import { action } from 'storybook';import { action } from '@storybook/addon-actions';
Quickstart
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
interface ButtonProps {
/**
* Is this the principal call to action on the page?
*/
primary?: boolean;
/**
* What background color to use
*/
backgroundColor?: string;
/**
* How large should the button be?
*/
size?: 'small' | 'medium' | 'large';
/**
* Button contents
*/
label: string;
/**
* Optional click handler
*/
onClick?: () => void;
}
const Button: React.FC<ButtonProps> = ({ primary = false, size = 'medium', backgroundColor, label, ...props }) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
return (
<button
type="button"
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
style={{ backgroundColor }}
{...props}
>
{label}
</button>
);
};
// More on default exports: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
const meta: Meta<typeof Button> = {
title: 'Example/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
backgroundColor: { control: 'color' },
},
args: {
onClick: () => console.log('Button clicked'), // Example action
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
};
export const Secondary: Story = {
args: {
label: 'Button',
},
};
export const Large: Story = {
args: {
size: 'large',
label: 'Button',
},
};
export const Small: Story = {
args: {
size: 'small',
label: 'Button',
},
};