Skip to content

Modern React 27: Storybook and component workbenches

This is part 27 of the Modern React development series.

Storybook gives components a place to be developed and reviewed outside the full application flow. A story is a named example state, which makes rare, loading, empty, and error states easier to see on purpose.

Concept

A component workbench renders a component with controlled inputs and fixtures. Storybook organizes those workbench examples as stories, then adds tooling for docs, interaction tests, visual review, and design system development.

Terms

  • Story: A named example of a component with specific props and state.
  • Args: Storybook’s serializable inputs for controlling component props.
  • Fixture: Stable sample data used to render a component state.
  • Workbench: A focused environment for exercising a component outside the product path.

Mental model

Think of Storybook as a component lab bench. The application route is the field test. The story is where you put one component under clear lighting and change its inputs deliberately.

How it is used

Use Storybook for design system components, dense states that are hard to reach through the app, visual regression review, accessibility checks, and component API discussion with designers and product peers.

How to use it

  1. Write stories for the normal state first.
  2. Add stories for loading, empty, error, disabled, long text, and permission variants.
  3. Use fixtures that resemble real data without exposing production data.
  4. Keep story args aligned with the component’s public API.
  5. Add interaction tests for important component flows when Storybook is part of the test path.

Example: Component stories

import type { Meta, StoryObj } from "@storybook/react";
import { ProductCard } from "./ProductCard";
const meta = {
component: ProductCard,
title: "Catalog/ProductCard",
} satisfies Meta<typeof ProductCard>;
export default meta;
type Story = StoryObj<typeof meta>;
export const InStock: Story = {
args: {
name: "Trail shoes",
priceCents: 12900,
inStock: true,
},
};
export const BackSoon: Story = {
args: {
name: "Rain shell",
priceCents: 9900,
inStock: false,
},
};
React output

Trail shoes

$ 129.00

In stock

Each story names a useful component state and passes props through the public component API.

Example: Interaction story

import { expect, userEvent, within } from "@storybook/test";
export const OpensMenu: Story = {
args: {
label: "Account",
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole("button", { name: /account/i }));
await expect(canvas.getByRole("menu")).toBeVisible();
},
};
Runtime result
export const OpensMenu: Story = {
  args: {
    label: "Account",
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.click(canvas.getByRole("button", { name: /account/i }));
    await expect(canvas.getByRole("menu")).toBeVisible();
  },
};

The story can double as a small interaction check for a state that reviewers can also inspect visually.

Details to watch

  • Public API: Stories should use the component like a consumer would, not reach into internals.
  • Fixture privacy: Use realistic shapes, not real customer data or credentials.
  • Coverage: Storybook complements app tests. It does not replace route-level flows or production monitoring.
  • State inventory: Stories are most valuable when they include states that the product flow hides.

Series navigation

References