mrkeyoor.com_
Wed 05 Aug 05:07 UTC
npmTestingupdated 05 Aug 2026

storybook

A frontend workshop that runs your UI components in isolation, outside your app. You describe each state of a component as a story (a small exported object), and Storybook serves a browsable catalog of those states with knobs for props, generated docs, and interaction tests that click through the rendered component. It supports React, Vue 3, Angular, Svelte, web components, and more, and doubles as the shared reference for design systems: developers build against it, QA tests against it, designers review in it.

Verdict

The default component workshop, and clearly worth it for design systems and component-heavy teams. For a small app it is a second build system you have to babysit; Ladle or plain Vitest may be the better trade.

API stability3/5The CSF story format has been stable for years, but majors land roughly yearly (8, 9, 10) and each one moves packages, imports, or config; official codemods soften the blow but you still run them.
Docs4/5storybook.js.org docs are thorough with per-framework code toggles; the pain is third-party tutorials and old answers that reference pre-9 package names like @storybook/test.
Maintenance5/5Pushed to within hours of this review, 90k+ stars, steady releases across the 10.x line, and a large contributor base plus corporate sponsorship.
Ecosystem5/5Large addon catalog and renderer support spanning React, Angular, Vue 3, Svelte, web components, and Ember, with extensions for React Native and mobile platforms per the README.

Use it if

  • You maintain a design system or shared component library and need a living, clickable catalog of every component state
  • You want interaction tests (play functions) and visual regression running against real rendered components, not jsdom approximations
  • Designers, PMs, or QA need to review component states without running the whole app locally
  • You want prop documentation generated from your components via autodocs instead of hand-written docs that drift
Skip it if

Setup reality

npx storybook@latest init detects your framework, scaffolds a .storybook directory and example stories, and usually works on the happy path (Vite plus React or Vue). The honest part: it installs a cluster of @storybook/* packages that must stay on identical versions, so you upgrade with npx storybook@latest upgrade rather than editing package.json by hand. Because Storybook has its own builder pipeline, anything custom in your app build (path aliases, PostCSS, Tailwind, global providers) has to be mirrored in .storybook/main and .storybook/preview. Monorepos and nonstandard setups are where init falls over and you end up reading framework-specific docs.

Patterns

Add Storybook to an existing appinit-project

npx storybook@latest init
npm run storybook  # dev server on :6006

Run it inside the app so it can detect your framework and builder; on monorepos run it in the package, not the repo root.

Write a CSF3 storybasic-story

import type { Meta, StoryObj } from "@storybook/react-vite";
import { Button } from "./Button";

const meta = {
  title: "UI/Button",
  component: Button,
} satisfies Meta<typeof Button>;
export default meta;

type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: { variant: "primary", children: "Save" },
};

Since Storybook 9, import types from your framework package (@storybook/react-vite here), not the bare renderer package.

Multiple states of one componentstory-variants

export const Primary: Story = {
  args: { variant: "primary", children: "Save" },
};

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

Spread another story's args to avoid repeating the base props; each named export becomes a sidebar entry.

Customize the controls panelcontrols-argtypes

const meta = {
  component: Button,
  argTypes: {
    variant: {
      control: "select",
      options: ["primary", "secondary", "ghost"],
    },
    onClick: { action: "clicked" },
  },
} satisfies Meta<typeof Button>;

Controls are inferred from prop types automatically; argTypes is only needed to override the inferred widget.

Wrap stories in a provider or layoutdecorator-wrapper

const meta = {
  component: UserCard,
  decorators: [
    (Story) => (
      <ThemeProvider theme="light">
        <div style={{ padding: 24 }}>
          <Story />
        </div>
      </ThemeProvider>
    ),
  ],
} satisfies Meta<typeof UserCard>;

Decorators on meta apply to every story in the file; put app-wide providers in .storybook/preview instead.

Interaction test with a play functioninteraction-test

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

export const SubmitsForm: Story = {
  args: { onSubmit: fn() },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    await userEvent.type(canvas.getByLabelText("Email"), "a@b.co");
    await userEvent.click(canvas.getByRole("button", { name: "Submit" }));
    await expect(args.onSubmit).toHaveBeenCalledOnce();
  },
};

Since v9 the test utilities live in storybook/test (no @); old imports from @storybook/test or @storybook/jest fail after upgrade.

Spy on a callback propmock-callback-spy

import { fn } from "storybook/test";

export const Clickable: Story = {
  args: { onClick: fn() },
};

fn() spies show every call in the Actions panel and are assertable inside play functions.

Generate a docs page from storiesautodocs

const meta = {
  component: Button,
  tags: ["autodocs"],
} satisfies Meta<typeof Button>;

Adds a Docs entry with a prop table and all stories; set the tag globally in .storybook/preview to enable it everywhere.

Global parameters and decoratorsglobal-config

// .storybook/preview.ts
import type { Preview } from "@storybook/react-vite";

const preview: Preview = {
  parameters: {
    backgrounds: { default: "dark" },
    layout: "centered",
  },
};
export default preview;

preview.ts is for what stories render with; .storybook/main.ts is for addons, framework, and builder config. Mixing them up is the classic config error.

Build a deployable static Storybookbuild-static

npx storybook build
# output in storybook-static/, host it anywhere static

The static build is how you share with designers and run visual regression in CI; it can be slow on big projects, so cache node_modules and the .cache dir.

Upgrade Storybook safelyupgrade-version

npx storybook@latest upgrade

Always use the upgrade command instead of bumping versions by hand; it runs codemods and keeps every @storybook/* package on the same version.

Alternatives

PackageRegistryPick it when
ladlenpmReact-only teams on Vite who want stories with a fraction of the install and startup weight
histoirenpmVue or Svelte teams on Vite who want a native-feeling story workshop
react-cosmosnpmYou want a component playground driven by fixtures rather than the full Storybook docs-and-addons machine