storybook
Storybook 8 Best Practices
Setup
npx storybook@latest init
This auto-detects your framework (React, Vue, etc.) and configures everything.
Writing Stories
Basic Story (CSF3)
// button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./button";
const meta = {
title: "Components/Button",
component: Button,
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["primary", "secondary", "danger"],
},
size: {
control: "radio",
options: ["sm", "md", "lg"],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
variant: "primary",
children: "Click me",
},
};
export const Secondary: Story = {
args: {
variant: "secondary",
children: "Cancel",
},
};
export const Disabled: Story = {
args: {
...Primary.args,
disabled: true,
},
};
Key Concepts
- Meta — default configuration for all stories in the file (component, title, argTypes).
- Story — a specific state of the component with defined
args. - Args — props passed to the component, editable via Controls panel.
- Tags —
["autodocs"]auto-generates a documentation page.
Args and Controls
Controls are auto-generated from TypeScript props. Customize them with argTypes:
argTypes: {
onClick: { action: "clicked" }, // log to Actions panel
color: { control: "color" }, // color picker
count: { control: { type: "range", min: 0, max: 100 } },
status: {
control: "select",
options: ["idle", "loading", "error"],
description: "Current status of the widget",
table: { defaultValue: { summary: "idle" } },
},
},
Decorators
Wrap stories with providers, layouts, or context:
// Per-story
export const WithTheme: Story = {
decorators: [
(Story) => (
<ThemeProvider theme="dark">
<Story />
</ThemeProvider>
),
],
};
// Global (in .storybook/preview.tsx)
const preview: Preview = {
decorators: [
(Story) => (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
),
],
};
Interaction Testing
Write tests inside stories using the play function:
import { expect, fn, userEvent, within } from "@storybook/test";
export const SubmitForm: Story = {
args: {
onSubmit: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByLabelText("Email"), "test@example.com");
await userEvent.type(canvas.getByLabelText("Password"), "password123");
await userEvent.click(canvas.getByRole("button", { name: "Sign in" }));
await expect(args.onSubmit).toHaveBeenCalledWith({
email: "test@example.com",
password: "password123",
});
},
};
export const ValidationError: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole("button", { name: "Sign in" }));
await expect(canvas.getByText("Email is required")).toBeInTheDocument();
},
};
- Import testing utilities from
@storybook/test(not directly from Testing Library). - Use
fn()for spy/mock functions with Actions panel integration. - Use
within(canvasElement)to scope queries to the story's render area. - Tests run in the browser and are visible in the Interactions panel.
Running Tests
# Run interaction tests via test-runner
npx test-storybook
# With coverage
npx test-storybook --coverage
Component Documentation
Autodocs
Add tags: ["autodocs"] to meta for auto-generated docs:
const meta = {
title: "Components/Button",
component: Button,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: "Primary UI button for user actions.",
},
},
},
} satisfies Meta<typeof Button>;
MDX Docs
For custom documentation pages:
{/* button.mdx */}
import { Canvas, Meta, Story } from "@storybook/blocks";
import \* as ButtonStories from "./button.stories";
<Meta of={ButtonStories} />
# Button
Buttons trigger actions. Use primary for the main CTA and secondary for alternatives.
<Canvas of={ButtonStories.Primary} />
## Variants
<Canvas of={ButtonStories.Secondary} />
<Canvas of={ButtonStories.Disabled} />
Story Organization
File Structure
Colocate stories with components:
components/
└── button/
├── button.tsx
├── button.stories.tsx
├── button.test.tsx
└── index.ts
Naming Hierarchy
// title creates the sidebar structure
title: "Components/Forms/Input"; // Components > Forms > Input
title: "Components/Button"; // Components > Button
title: "Pages/Dashboard"; // Pages > Dashboard
Parameters
Configure story-level or global behavior:
export const Mobile: Story = {
parameters: {
viewport: { defaultViewport: "mobile1" },
layout: "fullscreen",
},
};
export const DarkMode: Story = {
parameters: {
backgrounds: { default: "dark" },
},
};
Loaders
Fetch data before a story renders:
export const WithData: Story = {
loaders: [
async () => ({
users: await fetch("/api/users").then((r) => r.json()),
}),
],
render: (args, { loaded: { users } }) => <UserList users={users} {...args} />,
};
Configuration
Main Config
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(ts|tsx|mdx)"],
addons: ["@storybook/addon-essentials", "@storybook/addon-interactions", "@storybook/addon-a11y"],
framework: "@storybook/react-vite",
typescript: {
reactDocgen: "react-docgen-typescript",
},
};
export default config;
Useful Addons
| Addon | Purpose |
|---|---|
@storybook/addon-essentials |
Controls, Actions, Viewport, Backgrounds, Docs |
@storybook/addon-interactions |
Step-through interaction testing |
@storybook/addon-a11y |
Accessibility audit via axe-core |
@storybook/addon-designs |
Embed Figma designs alongside stories |
@chromatic-com/storybook |
Visual regression testing with Chromatic |
Patterns
- One story per meaningful state — Primary, Disabled, Loading, Error, Empty, WithData.
- Use args composition — spread a base story's args into variants instead of duplicating.
- Test edge cases — long text, empty data, error states, loading states, RTL.
- Use decorators for context — providers, layouts, themes.
- Keep stories independent — each story should work in isolation.
- Add
autodocsto all public components for automatic documentation.
More from grahamcrackers/skills
bulletproof-react-patterns
Bulletproof React architecture patterns for scalable, maintainable applications. Covers feature-based project structure, component patterns, state management boundaries, API layer design, error handling, security, and testing strategies. Use when structuring a React project, designing application architecture, organizing features, or when the user asks about React project structure or scalable patterns.
46react-aria-components
React Aria Components patterns for building accessible, unstyled UI with composition-based architecture. Covers component structure, styling with Tailwind and CSS, render props, collections, forms, selections, overlays, and drag-and-drop. Use when building accessible components, using react-aria-components, creating design systems, or when the user asks about React Aria, accessible UI primitives, or headless component libraries.
17clean-code-principles
Clean code principles for readable, maintainable TypeScript and React codebases. Covers naming, functions, abstraction, composition, error handling, comments, and code smells. Use when writing new code, refactoring, reviewing code quality, or when the user asks about clean code, readability, or maintainability.
10typescript-best-practices
Core TypeScript conventions for type safety, inference, and clean code. Use when writing TypeScript, reviewing TypeScript code, creating interfaces/types, or when the user asks about TypeScript patterns, conventions, or best practices.
9tanstack-query
TanStack Query v5 patterns for server state management, caching, mutations, optimistic updates, and query organization. Use when working with TanStack Query, React Query, server state, data fetching hooks, or when the user asks about caching strategies, query invalidation, or mutation patterns.
8zustand
Zustand state management patterns for React including store design, selectors, slices, middleware (immer, persist, devtools), and async actions. Use when managing client-side state, creating stores, working with Zustand, or when the user asks about global state management, store patterns, or state persistence.
7